#import "@preview/touying:0.6.1": * #import "@preview/cades:0.3.1": qr-code #import "@preview/codly:1.3.0": * #import "@preview/cjk-unbreak:0.2.0": remove-cjk-break-space, transform-childs #import "setoka.typ": * #show: codly-init.with() #show: remove-cjk-break-space #let plugin_tokenize_ja_uninitialized = plugin("plugins/tokenize-ja/tokenize-ja.wasm") #let plugin_tokenize_ja = plugin.transition(plugin_tokenize_ja_uninitialized.init) #let tokenize(s) = { cbor(plugin_tokenize_ja.tokenize(bytes(s))) } #let get-inner-str(e) = { if e.func() == text { if e.has("text") { e.text } else if e.has("body") { e.body } else { none } } else { none } } #let make-助詞-small(rest) = { rest = transform-childs(rest, make-助詞-small) if utils.is-sequence(rest) { for child in rest.children { let s = get-inner-str(child) if s != none { for t in tokenize(s) { if t.at(1) == "助詞" { [#set text(size: 0.9em);#t.at(0)] } else { t.at(0) } } } else { child } } } else { rest } } #show: make-助詞-small #show: setoka-theme.with( aspect-ratio: "16-9", config-info( title: [ Pure PHP で作る \ 簡易 HTTP サーバ ], subtitle: [PHP カンファレンス香川 2025], author: [nsfisis (いまむら)], date: datetime(year: 2025, month: 11, day: 24), ), config-common(preamble: { codly( fill: rgb("#eee"), lang-format: none, number-format: none, zebra-fill: none, ) }) ) #set text(font: "Noto Sans CJK JP", lang: "ja") #title-slide() #about-slide() #[ #set align(center + horizon) Pure PHP で作る \ 簡易 HTTP サーバ ] --- #[ #set text(size: 0.9em) - HTTP とは - サーバを実装する - GET - POST - Cookie ] --- #[ #set align(center + horizon) #set text(size: 2em) *HTTP* ] --- #strong[H]yper#strong[t]ext \ #strong[T]ransfer \ #strong[P]rotocol --- Hypertext #pause すごいテキスト #pause Hyperlink されたテキスト --- Hypertext 相互に接続され、\ 容易に参照できるテキスト --- Hyper でない text 書籍 --- #strong[H]yper#strong[t]ext \ #strong[T]ransfer \ #strong[P]rotocol --- #[ #set text(size: 0.9em) Transfer 転送 コンピュータ間でデータを送る ] --- #strong[H]yper#strong[t]ext \ #strong[T]ransfer \ #strong[P]rotocol --- Protocol #pause 予め定められた \ 取り決め・手順 --- #[ #set text(size: 0.7em) 挨拶プロトコル #pause A「こんにちは」#pause \ B「こんにちは」#pause 相手の言葉を少し遅れて繰り返す --- 挨拶プロトコル A「こんにちは」#pause \ B「かけうどん」#pause 挨拶失敗 \ プロトコルを守っていない ] --- #[ #set text(size: 0.9em) #strong[H]yper#strong[t]ext \ #strong[T]ransfer \ #strong[P]rotocol #pause すごい文書を転送するための手順 ] --- 具体的な HTTP の手順 #pause #[ #set text(size: 0.7em) クライアント「`GET /index.html HTTP/1.1`」#pause \ サーバ「`HTTP/1.1 200 OK`」 #pause クライアントからのリクエストに対し \ サーバがレスポンスを返す ] --- プロトコルスタック --- #[ #set text(size: 0.6em) - HTTP - Hypertext Transfer Protocol #pause - TCP - Transmission Control Protocol #pause - IP - Internet Protocol - ... #pause TCP/IP プロトコルスタック ] --- プロトコルスタック 下の階層・上の階層について \ 気にしなくてもよい --- #[ #set align(center + horizon) HTTP サーバを作る ] --- HTTP のバージョン - *HTTP/1.1* - HTTP/2 - HTTP/3 --- #[ #set text(size: 0.9em) HTTP/1.1 #link("https://datatracker.ietf.org/doc/html/rfc9112")[RFC 9112] (など) HTTP/1.1 の具体的なプロトコルを \ 定めた標準規格 --- クライアントがリクエストを送る サーバがレスポンスを返す ] --- #[ #set text(size: 0.7em) リクエスト - リクエスト行 + CRLF - 任意個数のフィールド行 + CRLF - CRLF - メッセージボディ --- リクエスト行 - メソッド - GET、POST、... - リクエストターゲット - HTTP バージョン ``` GET /index.html HTTP/1.1 ``` --- フィールド行 - フィールド名 - `:` - フィールド値 ``` Host: example.com ``` --- リクエスト - リクエスト行 + CRLF - 任意個数のフィールド行 + CRLF - CRLF - メッセージボディ --- レスポンス - ステータス行 + CRLF - 任意個数のフィールド行 + CRLF - CRLF - メッセージボディ --- ステータス行 - HTTP バージョン - ステータスコード - リーズンフレーズ (reason phrase) ``` HTTP/1.1 200 OK ``` --- GET メソッドに応答する ``` GET /get/ HTTP/1.1 ``` ``` HTTP/1.1 200 OK <本文> ``` ] --- #[ #set text(size: 0.5em) #set align(center + horizon) https://t.nil.ninja/phpcon-kagawa-2025/get/ #qr-code("https://t.nil.ninja/phpcon-kagawa-2025/get/", width: 9cm) ] --- #[ #set text(size: 0.5em) #set align(center + horizon) #link("https://github.com/nsfisis/nil.ninja/blob/67094790d2d9db5c99e7c136f49061a78698e57d/vhosts/t/phpcon-kagawa-2025/src/Http/Server.php")[主な実装ファイル] #qr-code("https://github.com/nsfisis/nil.ninja/blob/67094790d2d9db5c99e7c136f49061a78698e57d/vhosts/t/phpcon-kagawa-2025/src/Http/Server.php", width: 9cm) ] --- フォームを送れるように \ しよう --- #[ #set text(size: 0.9em) POST メソッド #pause プロトコルレベルでは GET と \ ほぼ同じ ] --- #[ #set text(size: 0.5em) #set align(center + horizon) https://t.nil.ninja/phpcon-kagawa-2025/post/ #qr-code("https://t.nil.ninja/phpcon-kagawa-2025/post/", width: 9cm) ] --- #[ #set text(size: 0.9em) GET・POST の違い #[ #set text(size: 0.7em) プロトコルレベルでは\ ほとんど同じ セマンティクス・慣例的な\ 使われ方が異なっている ] ] --- HTTP における状態の管理 --- #[ #set text(size: 0.6em) うどん店 (セルフでない) #pause - 客1「かけ」#pause - 店員「かけ一つ」#pause - 客2「釜玉」#pause - 店員「釜玉一つ」#pause - 客1「以上で」#pause - 店員「まだご注文頂いていません」 ] --- やり取りのたびに記憶を失う ステートレス --- 解決方法 - クッキー - セッション --- #[ #set text(size: 0.6em) クッキー #pause - 客1「かけ」#pause - 店員「かけ一つ」#pause - 客2「かけと釜玉」#pause - 店員「かけ一つと釜玉一つ」#pause - 客1「かけと釜玉。以上で」#pause - 店員「かけ一つと釜玉一つ。以上ですね」 --- クッキー - 客1「かけ」 - 店員「かけ一つ」`Set-Cookie: order="かけ"` - 客2「かけと釜玉」`Cookie: order="かけ"` - 店員「かけ一つと釜玉一つ」\ `Set-Cookie: order="かけ,釜玉"` - 客1「かけと釜玉。以上で」`Cookie: order="かけ,釜玉"` - 店員「かけ一つと釜玉一つ。以上ですね」 --- セッション #pause - 番号札を渡す (23)#pause - 客1「番号23。かけ」#pause - 店員「かけ一つ」番号23: かけ#pause - 客2「番号23。釜玉」#pause - 店員「かけ一つと釜玉一つ」番号23: かけ,釜玉#pause - 客1「番号23。以上で」#pause - 店員「かけ一つと釜玉一つ。以上ですね」 --- セッション - 番号札を渡す (23)`Set-Cookie: session_id="23"` - 客1「番号23。かけ」`Cookie: session_id="23"` - 店員「かけ一つ」番号23: かけ - 客2「番号23。釜玉」`Cookie: session_id="23"` - 店員「かけ一つと釜玉一つ」番号23: かけ,釜玉 - 客1「番号23。以上で」`Cookie: session_id="23"` - 店員「かけ一つと釜玉一つ。以上ですね」 ] --- #[ #set text(size: 0.5em) #set align(center + horizon) https://t.nil.ninja/phpcon-kagawa-2025/cookie/ #qr-code("https://t.nil.ninja/phpcon-kagawa-2025/cookie/", width: 9cm) ] --- #[ #set align(center + horizon) #[ #set text(size: 2em) まとめ ] --- #[ #set text(size: 2em) HTTP ] --- 技術を理解する \ 最も確実な方法は \ *実装*すること --- HTTP は手軽に実装できる\ プロトコル --- HTTP を手軽に実装しよう --- #[ #set align(top + left) #set text(size: 0.7em) - PHPerKaigi 2026 (3/20-22) - プロポーザル募集中 - PHP カンファレンス小田原 2026 (4/11) - プロポーザル募集開始予定 - PHP カンファレンス愛媛 2026 (10/3) - 準備中 ] --- ご静聴 \ ありがとうございました ]