Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
サーバーフレームワークの仕組みが気になったので車輪の再発明をしてみた
Search
Yuta Tomiyama
March 12, 2022
Programming
0
200
サーバーフレームワークの仕組みが気になったので車輪の再発明をしてみた
2022/03/12 全国学生エンジニア交流会「NSEEM」にて発表
https://github.com/yt8492/NativeServer
Yuta Tomiyama
March 12, 2022
Tweet
Share
More Decks by Yuta Tomiyama
See All by Yuta Tomiyama
モバイルアプリ開発を始めよう!
yt8492
0
47
Git勉強会
yt8492
0
110
なんでもやってみる勇気
yt8492
0
84
Android Autoが思ったよりしんどい話
yt8492
0
200
apollo-kotlinにcontributeした話
yt8492
0
130
DMM TVのSDカードダウンロード機能を実装した話
yt8492
1
830
今だからこそ知りたいKotlin Multiplatform
yt8492
0
290
State management and API calls in Jetpack Compose: Learning Apollo + Jetpack Compose through React Hooks
yt8492
0
1.2k
Compose for Webを始めよう
yt8492
0
400
Other Decks in Programming
See All in Programming
Google Agent Development Kit でLINE Botを作ってみた
ymd65536
2
250
脱Riverpod?fqueryで考える、TanStack Queryライクなアーキテクチャの可能性
ostk0069
0
130
新メンバーも今日から大活躍!SREが支えるスケールし続ける組織のオンボーディング
honmarkhunt
5
7.2k
ニーリーにおけるプロダクトエンジニア
nealle
0
830
効率的な開発手段として VRTを活用する
ishkawa
0
140
チームのテスト力を総合的に鍛えて品質、スピード、レジリエンスを共立させる/Testing approach that improves quality, speed, and resilience
goyoki
5
870
ソフトウェア品質を数字で捉える技術。事業成長を支えるシステム品質の マネジメント
takuya542
1
13k
今ならAmazon ECSのサービス間通信をどう選ぶか / Selection of ECS Interservice Communication 2025
tkikuc
21
4k
Porting a visionOS App to Android XR
akkeylab
0
460
iOS 26にアップデートすると実機でのHot Reloadができない?
umigishiaoi
0
130
Rubyでやりたい駆動開発 / Ruby driven development
chobishiba
1
700
Rails Frontend Evolution: It Was a Setup All Along
skryukov
0
140
Featured
See All Featured
The Invisible Side of Design
smashingmag
301
51k
Bash Introduction
62gerente
613
210k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
Become a Pro
speakerdeck
PRO
29
5.4k
Why You Should Never Use an ORM
jnunemaker
PRO
58
9.4k
Typedesign – Prime Four
hannesfritz
42
2.7k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
48
2.9k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
138
34k
Documentation Writing (for coders)
carmenintech
72
4.9k
Building Better People: How to give real-time feedback that sticks.
wjessup
367
19k
Making the Leap to Tech Lead
cromwellryan
134
9.4k
Transcript
サーバーフレームワークの 仕組みが気になったので 車輪の再発明をしてみた 2022/03/12 NSEEM
自己紹介 HN: マヤミト ID: yt8492 会津大学 学部4年 OtakuProject運営 GitHub: https://github.com/yt8492
趣味: Kotlin, Twitter, ウマ娘 Twitter: yt8492
バックエンドの開発してる人
フレームワークの仕組み、気になりません?
じゃあ作ってみよう!w
今回のテーマ: サーバーフレームワーク自作
やること - TCPのソケット通信をラップしてHTTP通信を実装 - それをもとにフレームワークを実装 - 使用言語はKotlin/Native (苦行) - Cの標準関数のブリッジくらいしか提供されてないので逆に勉強になると思った
- もしこのLTを見ている人が同じことをしたいと思ったら素直に Kotlin/JVMでやることをおすすめしま す
HTTP通信 - HTTPの仕様はRFCで定義されている - 今回はHTTP/1.1でやるのでRFC2616 - TCPのリクエストをどのように解釈して、レスポンスをどのような形式で送ればいいかなどが書かれ ている - リクエストを解釈してレスポンスを返せれば良い
- リクエスト - リクエストライン - ヘッダ - ボディ - レスポンス - ステータスライン - ヘッダ - ボディ
HTTPリクエストを解釈する - TCPのリクエストから以下を解釈できればよい - リクエストライン - ヘッダ - ボディ -
RFCではこのように定義されている - Request = Request-Line *(( general-header | request-header | entity-header ) CRLF) CRLF [ message-body ]
リクエストのリクエストラインを解釈する - RFCではこのように定義されている - Request-Line = Method SP Request-URI SP
HTTP-Version CRLF - メソッド、URI、HTTPバージョンが空白区切りで、最後に CRLF(改行)がある - つまり、ソケット通信で以下のことができればよさそう - CRLFが現れるまで通信を読み込む - 読み込んだバイト列を文字列として扱う - 空白で区切り、それをメソッド、 URI、HTTPバージョンとする
リクエストのリクエストラインを解釈する
リクエストのヘッダを解釈する - ヘッダフィールドのフォーマットは以下のように定義されている - 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のみの行がヘッ ダの終わりになる
リクエストのヘッダを解釈する - ソケット通信で以下のことができればよさそう - CRLFが現れるまで読み込む - 読み込んだ結果が空であればヘッダの終わり、そうでなければコロンで区切ってそれを名前と値と する - 終わりでない場合は繰り返す
リクエストのボディを解釈する - ボディは以下のように定義されている - message-body = entity-body | <Transfer-Encoding にてエンコードされた
entity-body> - ヘッダに Content-Length や Transfer-Encoding 各ヘッダフィールドを含むとボディが存在し、 な ければボディが空 - Content-Lengthが指定されている場合はそのバイト数だけボディをよみこむ
リクエストのボディを解釈する - 簡単のためTransfer-Encodingは今回考慮しないことに🙏
レスポンスをHTTPの形式にする - 以下の情報をTCPのレスポンスとして返せればよい - ステータスライン - ヘッダ - ボディ -
RFCではこのように定義されている - Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ]
ステータスラインをTCPのレスポンスに書き込む - RFCではこのように定義されている - Status-Line = HTTP-Version SP Status-Code SP
Reason-Phrase CRLF - つまり、ソケット通信で以下のことができるとよさそう - HTTPバージョンと区切りの空白を書き込む - ステータスコードと区切りの空白を書き込む - 説明句と終端のCRLFを書き込む
ヘッダをTCPのレスポンスに書き込む - 基本的にはリクエストと同じ - ヘッダフィールドの名前と値がコロン区切りになっていて、 CRLFで終わる - CRLFのみの行がヘッダの終わり
ボディをTCPのレスポンスに書き込む - 今回はTransfer-Encodingを考慮しないので単純にボディとして渡されたバイト列 をそのまま書き込む
ここまでがRFCに沿った実装
ここからフレームワークとしての便利実装
フレームワークなら便利であってほしい - HTTPを最低限実装しただけではバックエンド開発に使うには厳しい - Nodeのhttpモジュールとかだけでやろうとするとしんどい - フレームワークなら次のような機能があってほしい - クエリパラメータ -
パスパラメータ - ルーティング
クエリパラメータの取得 - 今回は以下のようになっているという前提で扱う - URIを?で区切った後ろの部分 - 名前=値という形でフィールドが構成され、それが &で連結されている - 名前と値はURLエンコードされる
- 以下のように実装する - URIを?で区切った後ろの部分を取得する - 空でなければさらに &で区切る - 更に=で区切り、それぞれを URLデコードする
ルーティング - 今回はユーザーが以下のように使えるように実装する - get("/hoge") { … }, post("/fuga") {
… } のように、メソッドごとに関数が用意されていて、それ に パスを指定し、ラムダ関数の中で実際にリクエストを受け取ったときの処理を書く - 次のように実装する - ユーザーが get("/hoge") { … } のように関数を呼び出すとハンドラが追加される - ソケットから受け取った TCPのリクエストをHTTPのリクエストとして解釈する - 受け取ったリクエストの URIをもとにどのハンドラで処理するべきか評価し、最も評価度が高いハン ドラに実際に処理をさせる - 処理の結果ユーザーが生成したレスポンスのインスタンスを実際に HTTPのレスポンスとしてTCP のソケットに書き込む
ハンドラの実装 - ハンドラ自体はパスとHTTPメソッドと処理をする関数を持つだけ - ユーザーがHTTPのリクエストに対応した関数を呼び出すとハンドラのリストに追加 されていく
リクエストをハンドラにマッチさせる - 同じリクエストでも複数のハンドラにマッチする可能性がある - /hoge/fuga は /hoge/fuga, /hoge/:param, /hoge/* にマッチする可能性がある
- マッチ度が高いのは /hoge/fuga > /hoge/:param > /hoge/* - これを実際にリクエストが来るたびに行う - ハンドラのパスを/で区切り、それぞれが定数、パスパラメータ、ワイルドカードかど うかによってそのハンドラがマッチした場合の評価度を計算する - パスパラメータを持つ場合は実際にそれを取り出す - ソースコードは結構長くなったのでGitHubまで見に来て
使う側はこんな感じで使える
実際のソースコードはこちら - yt8492/NativeServer https://github.com/yt8492/NativeServer
まとめ - ソケット通信からサーバーフレームワークの実装できた - RFCを読みながら実装するとかなり勉強になる - 自分の知らなかった仕様に出会える - 実際に動くと感動
みんなも車輪の再発明しよう! 楽しいよ!