diniiに入社していました&就職活動、キャリアについて

8月1日に株式会社diniiに入社していました。前回の投稿 ののち、5月頃から就職活動を始めており、多くの会社の方やエージェントの方に面談の機会を作っていただきました。最終的にdiniiに入社することを決めましたが、自分の体がたくさんあれば入社したいと思う会社も多く、とても悩みながら決断しました。お時間を取っていただいたのに辞退した会社の方にはとても申し訳ないですが、自分のエンジニア人生の中で何かまたご縁があればと思っています。

就職活動について

今回の就職活動では、LinkedIn, Findy, Wantedly, リクルートダイレクトスカウトを主に利用していました。以前の転職活動では元々関心のあった企業の中からすぐに選考に進んでいたのですが、今回は幅広く企業の情報を集めた上で選考に進む企業を選んでいきたいと思っていたため、少しでも気になる企業や案件があれば積極的に面談を依頼していました。

企業の方と直接やり取りを行なってカジュアル面談や選考を行うケースと、エージェントの方を通じてカジュアル面談や選考を行うケースがありました。エージェントを経由した場合の利点として、求人の給与など条件面も教えていただけるので企業を比較しやすいこと、非公開の求人を紹介していただけるケースも多いことなどが挙げられます。欠点としては、エージェントの方との面談を通じて求人内容の詳細を教えていただけるという場合が多く、その求人内容が期待に合わないものであれば無駄な時間となってしまう点や、選考に関するやり取りが直接企業の方とできないため、伝言ゲーム的なコミュニケーションになってしまうという点があります。まとめると、エージェントを利用することで時間効率やコミュニケーションの精度は下がる部分がありますが、時間をかけても幅広く情報収集する場合には適していると考えます。

また、今回はスタートアップを中心に見ていました。当初は無数の企業からどうやって絞り込んでみていくかに苦労し、「すごいベンチャー100」などで企業情報を集めましたが、有力なベンチャーキャピタルの投資先となっている企業を一つ一つ見ていくというやり方もありかと感じました (これは後から思いついたものなので、今回はあまり実践できませんでしたが)。

キャリアについて

前職を退職後、チャレンジしていきたいと思ったのは「良いプロダクトを作ること」「良いプロダクトを作る組織を作ること」の2点でした。「良いプロダクトを作ること」については Duck の個人開発を通じて前に進めていますが、「良いプロダクトを作る組織を作ること」についても関心がありました。そこで、エンジニア組織全体、技術全体を見ることにチャレンジできるかという視点で就職活動を行いました。

入社時点において、diniiではフルタイムのエンジニアが10名前後で2つの開発チームを持っていました。この規模であれば各チーム、エンジニアの動きをある程度高い解像度で見ることができます。成長中の会社であり、チーム数が増えていくと自分から見える範囲も限られてきて組織の課題も変わっていくと考えられるため、組織の成長に合わせて自分も成長していく必要があり、それが面白そうだと思ったことが入社理由の一つにあります。

エンジニアのキャリアについて、ある程度キャリアを積むとIndivisual ContributorかManagerかという選択肢が挙がってくるかと思います。今回の自分の選択はどちらかといえばManager側になるのですが、今後ずっとManager的な動きをするかはわかりませんし、あまり決めたくないというのが正直なところです。自分が最も重みを置いている価値は「学び」であり、キャリアを限定せずにどちらにもなれるという感覚を持ち、その時点でより面白そうな選択をし続けることが最もモチベーションを高く維持でき、高いモチベーションによって学習効率が上がると考えているためです。

diniiに入社して、もしくはスタートアップについて

入社してからやってきたことについてはdiniiの技術ブログの方に書いていきたいと思いますが、1エンジニアとしても全社的な動きが見えやすく、自分のアクションが組織全体の課題解決に直結しているという実感を得られています。また、飲食店のオペレーションを支えるプロダクトを提供しており、セールスの方を通じてお客様からのフィードバックがよく届きますし、大きいインシデントが起きると飲食店のオペレーションが止まってしまうという緊張感もあります。

組織の規模が大きくなるほど、1エンジニアとしてのアクションが組織全体に与えるインパクトやプロダクトに与えるインパクトは相対的に小さくなるように感じます。もちろん組織規模や事業規模が大きければ、1メンバーや1ユーザーあたりから見たインパクトが小さくても全体のインパクトとしては大きくなるのですが、実感としては感じにくいということです。 技術を使って事業を成長させている、課題を解決しているという実感を得られることがモチベーションに大きく左右するという方には、スタートアップはお勧めだと言えるでしょう。

diniiでも絶賛採用中ですので、興味のある方はぜひこちらの採用情報を参照ください。 https://diniinote.notion.site/dinii-4f1f4687b47a48ee9de7abbe5a1f3814

メルカリを退職し、個人でWebアプリを作りました

メルカリでやっていたこと

自分がメルカリに入社したのは2017年12月で、SET(Software Engineer in Test)というポジションに応募して採用されました。 SETはその名の通りテストに対する課題を解決するための役割なのですが、当時のSETチームはテストの実装を行うわけではなく、開発環境や検証環境の運用やCI/CDツールの導入・サポートなどを主な役割としていました。

自分がSETとして応募したのは、前職までの経験で機能開発にやや飽きており、エンジニアとして品質の改善をテーマとして扱っていきたいと考えていたためでした。通常の機能開発ではプロジェクトの要件や期日に沿って開発することが求められますが、SETの業務には要件や期日はなく、何をいつどのように解決するかを自分で判断する必要がありました。チームメンバーやプロジェクトメンバーと話し合い、コードを見ながら現在の開発・運用状況を把握し、アプローチを提案して大まかな合意が取れれば実装し、詳細はPull Request中で議論するというような流れでした。

また、全社的な開発環境を運用しているため、社内のメンバーから開発環境に関するトラブルの報告があれば、そのメンバーと連絡を取ってトラブルシューティングや対策を行っていました。SETが行った設定変更等に原因があれば申し訳ないと感じるのですが、トラブルが解消するとメンバーからは感謝の言葉を述べられることが多く、若干複雑な気持ちになりつつも、積極的に感謝の言葉を言い合う文化なんだなあと感銘を受けていました。

しかし、入社して半年を過ぎたころにSETは独立した組織として解散する方針となり、自分はFrontendチーム所属となりました。同じタイミングでメルカリWeb版のre-architectureプロジェクトが開始しており、これに合わせてインフラもマイクロサービス化していく方針となりました。この時は想像していなかったのですが、このときに始まったマイクロサービス化が完了するまで4年かかり、その詳細は メルカリのエンジニアブログに記述しています。興味がある方はそちらを参照ください。

Web re-architectureプロジェクトでは、インフラ構築を主として担当しました。マイクロサービス化するにあたって、サービスの開発チームが運用も行っていくという方針を立てたため、SETでやっていたようなCI/CDの設定や開発環境の構築に加えて、本番環境のインフラ構築や運用ツールの設定、運用フローの構築なども行いました。会社としてもマイクロサービス化の事例があまりないタイミングでしたが、Kubernetesのインフラ上でHTTPリクエストをProxyするWeb Gatewayというマイクロサービスを作り、3-stepsリリースという段階的なリリースフロー を導入するなど、多くのチャレンジができました。

こうした業務は最初は一人で担当していましたが、メンバーが増えてDevOpsというチームになりました。その後、プロジェクトのリリースを経て育児休暇を取っていたのですが、育児休暇が明けた2019年9月にはWeb Backendのマイクロサービスを扱っていたチームと統合してWeb Platformチームとなり、自分の役割も一エンジニアからTech Leadとなりました。それからは自分で手を動かすだけでなく、チームの技術的なロードマップや開発・運用体制を作っていくようになりました。 扱っている技術範囲が広いチームだったので、チームが扱っているマイクロサービスや技術領域を分割し、それぞれの領域にMaintainerを割り当てるような体制にしたり、モブプログラミングや読書会を定期的に開いて学びあえる環境を作っていきました。新しい開発プロジェクトも始まり、サービスのインフラ構成を設計・構築したり、メルカリWebの認証画面を提供するマイクロサービスを立ち上げるなど、Webのフロントエンド・バックエンド・インフラをまたがって開発を行っていました。

2021年からはチームのEngineering Managerとなり、チームメンバーとの1on1や評価といったPeople Managementの役割や、チームのOKR作成や開発プロセスの運用を行うProject Managementの役割を持ちました。といっても、Managerになったタイミングではチームメンバーが少なくなっていたうえにプロジェクトの開発が佳境に入っていたため、手が足りない部分は自分が担当するなど、実質的にPlaying Managerとして働いていました。人手不足の状況を打開するため、チームのミッションを再定義し、新しいミッションに沿った形で採用の選考フローを見直し、採用活動を強化しました。Web Platformチームのミッションや役割については、メルカリのエンジニアブログから参照できます。チームメンバーの退職などもあり苦しい時期もありましたが、幸運にも素晴らしいメンバーたちを新たに採用でき、チームの活動も徐々に安定していきました。

退職

退職理由の一つはキャリアパスについてでした。Managerの方向でキャリアを伸ばすのであればManager of ManagerやDirectorなどがより上位の管理職となるのですが、現在のメルカリの規模のような会社で、より多くのチームやメンバーをマネジメントしていくことにあまり魅力を感じていませんでした。そこで、チームが安定したらEngineering Managerの役割を移譲し、Indivisual Contributer(IC)に戻ろうと考えていました。 しかし、チームが安定し、Engineering Managerの役割を移譲していく中で、「今が自分のキャリアを考える良いタイミングではないか」という考えが頭をよぎりました。Web Platformチームの業務には精通しており、チームとしても会社としても成熟度が高まっていましたが、自分からは入社当時の熱量がなくなっていると感じていました。改めて自分の今後のキャリアやチャレンジについて考える時間を取りたいと思い、退職を決めました。

メルカリでは、入社したときには予想できなかったほどの多くの経験を積むことができ、チームメンバーやマネージャーにも恵まれました。入社当時は初歩的な英語しか話せなかったのですが、メルカリのエンジニアチームがグローバル化する中で英語力も鍛えられ、業務であまり支障なく英語が使えるまで上達することができました。また、小さい子供を持つ親としてもとても働きやすい職場でもありました。誰もが当たり前のように育児休暇を取っており、自分が育児休暇を取ったときもチームメンバーからも暖かいメッセージを頂きました。コロナウイルスの流行後には100%リモートワークができる環境となり、長男を毎朝幼稚園のバス停まで送っていくのが日課になるなど、家族との時間を多く過ごすことができました。 自分は一度退職することを決めましたが、それは5年間の充実した時間の後に今後のキャリアを考えて決断したものであり、就職先として検討している方には自信を持って薦めることができます。

個人でのWebアプリケーション開発

退職後に何をしたいかと考えた際に、個人で何かWebアプリケーションの開発・運用をしたいと考えました。自分は業務としてWebアプリケーションの開発や運用をやっていましたが、個人としてWebアプリケーションを運用した経験はなく、単純にやってみたかったのです。

また、自分が使いたいアプリケーションのアイデアもありました。異なる用途のために複数のメモ用アプリケーションを利用していたのですが、1つのアプリケーションで大部分の用途に使えるものが欲しいと思っていました。 PC上でメモを記述する場合はコードブロックを利用できるMarkdown形式のメモ帳を好みましたが、メモを書く前に「ノートを作成する」もしくは「適切なノートを見つける」といった行為をする必要があるのが手間で、1-数行程度のちょっとしたメモを残す用途には適していないと感じていました。 Google Keepのようにさっとメモをつけられるアプリケーションも利用していましたが、メモを見つけるのが検索とタグ頼りになるのでしばしば過去のメモを見失いました。また、Markdown形式での記述はできません。 Workflowyという箇条書きでメモを書けるアプリケーションも利用していました。TODO管理がしやすいほか、特定のテーマについて箇条書きで階層構造をつけ、テーマについて考えたり調べたりしたことを掘り下げながら書いていくことができるツールで気に入っていましたが、すべての用途をカバーできるアプリケーションではありませんでした。

Slackをメモ用途に利用したときに、これはメモ用のアプリとして最適ではないかと考えました。短文のメモをさっと書くことができ、スレッドを利用することで一つのテーマについて多くの情報を記載でき、Markdown(っぽい構文)が使え、チャンネル、スレッド、検索機能などによりメモを見つけるのも容易でした。しかし残念なことに、Slackは基本的にメモアプリではなくコミュニケーション用のアプリであり、自分が投稿したデータは自分の管理下にはありません。また、Slackでは無料プランを利用していると一定期間しかメッセージを保持できません。データをexportするような機能もありません。これは自分の作成したメモをずっと使い続けたいと考えた場合のリスクでした。

そこで、Slackのように高機能なチャット形式のインターフェースを持ちつつも、個人のメモ用途に使えるアプリケーションを作れないかと考えました。メモという用途だと、オフラインでも完全に動作させたいと考えました。これをWebの技術で作ることは技術的にも多くのチャレンジがあるように感じ、このアイデアを実現したWebアプリケーションを作成することを決めました。

開発は、まず大まかなアーキテクチャや要素技術を決め、それから機能の設計・実装を行っていきました。アーキテクチャの方針として "Offline First" を掲げました。オフラインでも動作するローカルデータベースと、データ同期用のリモートデータベースを利用しますが、リモートデータベースの送受信はすべてService Workerを通じてバックグラウンドで行い、ユーザーのデータ操作はすべてローカルデータベースを通じて行いました。 ローカルデータベースにはIndexedDBを利用しましたが、通常のデータベースのように1レコードあたり1データを登録していくと、データ数が10万件などある程度大規模になるとデータ追加やインデックスのないレコードの探索時の時間がかかりすぎたため、1レコードあたりに多数のデータを含むなどして最適化したデータ構造にしました。代案としてWebAssembly版のSQLiteも試したのですが、公式のものは大量のデータを保存するために Origin-Private FileSystem APIが必要であり、Safariが対応していなかったため採用しませんでした。また、Third-Party製のものでabsurd-sqlも試しましたが、IndexedDBのデータ構造を最適化した構成のほうがパフォーマンスが優れていた点や、メンテナンスに不安があった点からこちらも採用しませんでした。

また、リモートデータベースにはGoogle Driveを利用しています。これはメモアプリであるため、データのオーナーシップはあくまでユーザーにある形にしたいと考え、サーバー側でデータベースを持たない構成にしました。これはインフラのコストを下げ、サービスを無料で提供し続けても金銭的な負担にならないようにするためのものでもありました。 アプリケーションとしてどのようなデータを取り扱っているかについてはプライバシーポリシーに掲載しています。

利用規約やプライバシーポリシーの作成についても苦労した点であり、良いウェブサービスを支える「利用規約」の作り方 という本を読んだり、他のサービスの記述を参照したりしながら作成し、弁護士の方にも見ていただきました。弁護士や行政書士の方にプライバシーポリシーの作成やレビューを依頼することもできるのですが、今回の場合は初回無料で相談を受け付けている弁護士事務所に連絡し、初回の相談の中で修正箇所を指摘していただいたため、幸い金銭的な負担はかかりませんでした。

その他にも開発中苦労した点などは無数にあるのですが、それはまた別の機会に記述できればと思います。

Webアプリケーション "Duck" のリリース

作成したWebアプリケーションは "Duck" と名付けました。"Duck" は "Rubber Duck Debugging" から取った単語であり、チャット形式のメモアプリにより「何かについて説明/記述するという過程で、よりよいアイデアが生まれる」というコンセプトが実現できると考えたためです。

DuckのWebサイトは https://site.ducknote.app/ からアクセスできます。現在は英語版のみ提供していますが、興味があればぜひ使ってみていただけると嬉しいです。 サイト上の Open Duck ボタンをクリックしてアプリケーションのURLに遷移すると、Welcomeダイアログの後にGoogleログインのダイアログが出ますが、いきなりGoogleログインするのは抵抗のある方もいるかと思います。その場合はログインせずにダイアログを閉じればオフラインの挙動になるため、そのまま試しに使ってみてもらえばと思います。オフラインでローカルに保存したデータは、GoogleログインしたタイミングでGoogle Driveにアップロードでき、他の端末でも利用できるようになります。 もしアプリケーションの挙動などでに何か問題があれば @urahiroshi まで報告ください。

今後何をやっていくか

個人としては、そろそろ次の仕事探しを始めようかと考えています。 Duckは生計を立てるために作ったものではないですが、自分自身がヘビーユーザーになっているアプリでもあるので、仕事をしながらプライベートの時間で開発・運用を続けていきます。

NGINX reverse proxyのTCP通信の確認

NGINX reverse proxyの挙動を確認する。

こちらが検証用のリポジトリhttps://github.com/urahiroshi/play-nginx

http://localhost:8080 に接続すると、reverse proxy経由でhttpサーバに接続し、 http://localhost:8081 に接続すると、直接httpサーバに接続することができるようにしている。 http://localhost:8082 では、keepaliveの設定を行なったreverse proxy経由でhttpサーバに接続できる。 各コンテナには、TCP通信を確認するためのtcpdumpもインストールしている。

また、httpサーバ内のコンテンツは https://github.com/jakesgordon/javascript-tetris を利用している。 index.htmlと、それに参照されるjsファイル(stats.js)、jpegファイル(texture.jpg)が一つずつのみの構成である。

まず、 http://localhost:8081 にブラウザ(Chrome 68)からアクセスし、httpサーバに直接接続した場合の通信を確認する。 tcpdumpの出力に加えて、コメントを # に記載している。

# 3 way handshake
00:43:52.610289 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [S], seq 2038319000, win 29200, options [mss 1460,sackOK,TS val 9787176 ecr 0,nop,wscale 7], length 0
00:43:52.610341 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [S.], seq 2099843645, ack 2038319001, win 28960, options [mss 1460,sackOK,TS val 9787176 ecr 9787176,nop,wscale 7], length 0
00:43:52.610391 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 9787176 ecr 9787176], length 0

# `GET /` リクエストを受け取り、その応答を返す
00:43:52.613742 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [P.], seq 1:411, ack 1, win 229, options [nop,nop,TS val 9787177 ecr 9787176], length 410: HTTP: GET / HTTP/1.1
00:43:52.613762 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [.], ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 0
00:43:52.613893 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [P.], seq 1:242, ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 241: HTTP: HTTP/1.1 200 OK
00:43:52.613925 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 242, win 237, options [nop,nop,TS val 9787177 ecr 9787177], length 0
00:43:52.613969 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [.], seq 242:7482, ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 7240: HTTP
00:43:52.614004 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 7482, win 350, options [nop,nop,TS val 9787177 ecr 9787177], length 0
00:43:52.614016 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [.], seq 7482:14722, ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 7240: HTTP
00:43:52.614025 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 14722, win 463, options [nop,nop,TS val 9787177 ecr 9787177], length 0
00:43:52.614029 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [.], seq 14722:16626, ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 1904: HTTP
00:43:52.614038 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 16626, win 493, options [nop,nop,TS val 9787177 ecr 9787177], length 0
00:43:52.614055 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [P.], seq 16626:18125, ack 411, win 235, options [nop,nop,TS val 9787177 ecr 9787177], length 1499: HTTP
00:43:52.614067 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 18125, win 516, options [nop,nop,TS val 9787177 ecr 9787177], length 0

# `GET /stats.js` リクエストを受け取り、その応答を返す
00:43:52.674079 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [P.], seq 411:750, ack 18125, win 516, options [nop,nop,TS val 9787183 ecr 9787177], length 339: HTTP: GET /stats.js HTTP/1.1
00:43:52.674442 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [P.], seq 18125:18378, ack 750, win 243, options [nop,nop,TS val 9787183 ecr 9787183], length 253: HTTP: HTTP/1.1 200 OK
00:43:52.674510 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 18378, win 539, options [nop,nop,TS val 9787183 ecr 9787183], length 0
00:43:52.674551 IP f7de9f74a370.http > 172.21.0.1.41734: Flags [P.], seq 18378:22481, ack 750, win 243, options [nop,nop,TS val 9787183 ecr 9787183], length 4103: HTTP
00:43:52.674653 IP 172.21.0.1.41734 > f7de9f74a370.http: Flags [.], ack 22481, win 603, options [nop,nop,TS val 9787183 ecr 9787183], length 0
00:43:52.676882 IP 172.21.0.1.41736 > f7de9f74a370.http: Flags [S], seq 3284980702, win 29200, options [mss 1460,sackOK,TS val 9787183 ecr 0,nop,wscale 7], length 0
00:43:52.676936 IP f7de9f74a370.http > 172.21.0.1.41736: Flags [S.], seq 2052437592, ack 3284980703, win 28960, options [mss 1460,sackOK,TS val 9787183 ecr 9787183,nop,wscale 7], length 0
00:43:52.676969 IP 172.21.0.1.41736 > f7de9f74a370.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 9787183 ecr 9787183], length 0

# `GET /texture.jpg` リクエストを受け取り、その応答を返す。なお、 `GET /stats.js` のときとは異なる送信元ポートが使われていることに注意。
00:43:52.677619 IP 172.21.0.1.41736 > f7de9f74a370.http: Flags [P.], seq 1:379, ack 1, win 229, options [nop,nop,TS val 9787183 ecr 9787183], length 378: HTTP: GET /texture.jpg HTTP/1.1
00:43:52.677642 IP f7de9f74a370.http > 172.21.0.1.41736: Flags [.], ack 379, win 235, options [nop,nop,TS val 9787183 ecr 9787183], length 0
00:43:52.677802 IP f7de9f74a370.http > 172.21.0.1.41736: Flags [P.], seq 1:243, ack 379, win 235, options [nop,nop,TS val 9787183 ecr 9787183], length 242: HTTP: HTTP/1.1 200 OK
00:43:52.677838 IP 172.21.0.1.41736 > f7de9f74a370.http: Flags [.], ack 243, win 237, options [nop,nop,TS val 9787183 ecr 9787183], length 0
00:43:52.677866 IP f7de9f74a370.http > 172.21.0.1.41736: Flags [.], seq 243:7483, ack 379, win 235, options [nop,nop,TS val 9787183 ecr 9787183], length 7240: HTTP
00:43:52.677880 IP 172.21.0.1.41736 > f7de9f74a370.http: Flags [.], ack 7483, win 350, options [nop,nop,TS val 9787183 ecr 9787183], length 0
00:43:52.677892 IP f7de9f74a370.http > 172.21.0.1.41736: Flags [.], seq 7483:14723, ack 379, win 235, options [nop,nop,TS val 9787183 ecr 9787183], length 7240: HTTP

GET /GET /stats.js は、送信元ポートが同じ(41734)であるが、これは Connection: keep-alive ヘッダによりTCPコネクションが再利用されたためである。3 way handshakeが不要な分、早くレスポンスを返せるようになっている。 GET /texture.jpg では異なる送信元ポート(41736)が使用されている。これはChromeの挙動で、htmlが必要とする複数のassetsを並列で取得できるように、異なるポートを利用してリクエストを投げているのだろう。

次に、 http://localhost:8080 にアクセスし、revese proxy内の通信を確認してみる。 なお、ブラウザのキャッシュは無視したいので、secret windowを用いる。

# 3 way handshake (client <> proxy)
02:04:27.062322 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [S], seq 2771945777, win 29200, options [mss 1460,sackOK,TS val 11296929 ecr 0,nop,wscale 7], length 0
02:04:27.062525 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [S.], seq 2890086300, ack 2771945778, win 28960, options [mss 1460,sackOK,TS val 11296929 ecr 11296929,nop,wscale 7], length 0
02:04:27.062567 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 11296929 ecr 11296929], length 0

# `GET /` request (client -> proxy)
02:04:27.063173 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [P.], seq 1:411, ack 1, win 229, options [nop,nop,TS val 11296929 ecr 11296929], length 410: HTTP: GET / HTTP/1.1
02:04:27.063227 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [.], ack 411, win 235, options [nop,nop,TS val 11296929 ecr 11296929], length 0

# 3 way handshake (proxy <> server)
02:04:27.063973 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [S], seq 835116420, win 29200, options [mss 1460,sackOK,TS val 11296929 ecr 0,nop,wscale 7], length 0
02:04:27.064036 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [S.], seq 1521447049, ack 835116421, win 28960, options [mss 1460,sackOK,TS val 11296929 ecr 11296929,nop,wscale 7], length 0
02:04:27.064055 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 11296929 ecr 11296929], length 0

# `GET /` request (proxy -> server)
02:04:27.064124 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 1:513, ack 1, win 229, options [nop,nop,TS val 11296929 ecr 11296929], length 512: HTTP: GET / HTTP/1.0
02:04:27.064140 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [.], ack 513, win 235, options [nop,nop,TS val 11296929 ecr 11296929], length 0

# `GET /` response (server -> proxy -> client)
02:04:27.099279 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [P.], seq 1:237, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296929], length 236: HTTP: HTTP/1.1 200 OK
02:04:27.099295 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 237, win 237, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100057 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [.], seq 237:7477, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 7240: HTTP
02:04:27.100091 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 7477, win 350, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100135 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [.], seq 7477:14717, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 7240: HTTP
02:04:27.100145 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 14717, win 463, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100167 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [P.], seq 1:4102, ack 411, win 235, options [nop,nop,TS val 11296933 ecr 11296929], length 4101: HTTP: HTTP/1.1 200 OK
02:04:27.100163 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [.], seq 14717:16621, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 1904: HTTP
02:04:27.100167 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 16621, win 493, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100192 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [P.], seq 16621:18120, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 1499: HTTP
02:04:27.100207 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [.], ack 4102, win 293, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100212 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 18120, win 516, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100263 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [.], seq 4102:11342, ack 411, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 7240: HTTP
02:04:27.100291 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [.], ack 11342, win 406, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100300 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [P.], seq 11342:18125, ack 411, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 6783: HTTP
02:04:27.100300 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [F.], seq 18120, ack 513, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100313 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [.], ack 18125, win 512, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100338 IP b4f40c2642a2.54492 > play-nginx_http-server_1.play-nginx_default.80: Flags [F.], seq 513, ack 18121, win 516, options [nop,nop,TS val 11296933 ecr 11296933], length 0
02:04:27.100401 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54492: Flags [.], ack 514, win 235, options [nop,nop,TS val 11296933 ecr 11296933], length 0

# `GET /stats.js` request (client -> proxy)
02:04:27.364459 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [P.], seq 411:750, ack 18125, win 512, options [nop,nop,TS val 11296959 ecr 11296933], length 339: HTTP: GET /stats.js HTTP/1.1
02:04:27.364491 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [.], ack 750, win 243, options [nop,nop,TS val 11296959 ecr 11296959], length 0

# 3 way handshake (proxy <> server)
02:04:27.364645 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [S], seq 2056379865, win 29200, options [mss 1460,sackOK,TS val 11296959 ecr 0,nop,wscale 7], length 0
02:04:27.364695 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [S.], seq 3671721006, ack 2056379866, win 28960, options [mss 1460,sackOK,TS val 11296959 ecr 11296959,nop,wscale 7], length 0
02:04:27.364709 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 11296959 ecr 11296959], length 0

# `GET /stats.js` request (proxy -> server)
02:04:27.364761 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 1:442, ack 1, win 229, options [nop,nop,TS val 11296959 ecr 11296959], length 441: HTTP: GET /stats.js HTTP/1.0
02:04:27.364775 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [.], ack 442, win 235, options [nop,nop,TS val 11296959 ecr 11296959], length 0

# `GET /stats.js` response (server -> proxy -> client)
02:04:27.364943 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [P.], seq 1:249, ack 442, win 235, options [nop,nop,TS val 11296959 ecr 11296959], length 248: HTTP: HTTP/1.1 200 OK
02:04:27.364996 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 249, win 237, options [nop,nop,TS val 11296959 ecr 11296959], length 0
02:04:27.365823 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [P.], seq 249:4352, ack 442, win 235, options [nop,nop,TS val 11296960 ecr 11296959], length 4103: HTTP
02:04:27.365880 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 4352, win 301, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.365969 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [F.], seq 4352, ack 442, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.365999 IP b4f40c2642a2.80 > 172.21.0.1.53480: Flags [P.], seq 18125:22481, ack 750, win 243, options [nop,nop,TS val 11296960 ecr 11296959], length 4356: HTTP: HTTP/1.1 200 OK
02:04:27.366038 IP 172.21.0.1.53480 > b4f40c2642a2.80: Flags [.], ack 22481, win 580, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.366067 IP b4f40c2642a2.54494 > play-nginx_http-server_1.play-nginx_default.80: Flags [F.], seq 442, ack 4353, win 301, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.366096 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54494: Flags [.], ack 443, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0

# 3 way handshake (client <> proxy)
02:04:27.367508 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [S], seq 1748261451, win 29200, options [mss 1460,sackOK,TS val 11296960 ecr 0,nop,wscale 7], length 0
02:04:27.367542 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [S.], seq 997585658, ack 1748261452, win 28960, options [mss 1460,sackOK,TS val 11296960 ecr 11296960,nop,wscale 7], length 0
02:04:27.367573 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 11296960 ecr 11296960], length 0

# `GET /texture.jpg` request (client -> proxy)
02:04:27.367836 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [P.], seq 1:379, ack 1, win 229, options [nop,nop,TS val 11296960 ecr 11296960], length 378: HTTP: GET /texture.jpg HTTP/1.1
02:04:27.367875 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [.], ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0

# 3 way handshake (proxy <> server)
02:04:27.367957 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [S], seq 726545254, win 29200, options [mss 1460,sackOK,TS val 11296960 ecr 0,nop,wscale 7], length 0
02:04:27.368005 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [S.], seq 1146397801, ack 726545255, win 28960, options [mss 1460,sackOK,TS val 11296960 ecr 11296960,nop,wscale 7], length 0
02:04:27.368023 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 11296960 ecr 11296960], length 0

# `GET /texture.jpg` request (proxy -> server)
02:04:27.368088 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 1:481, ack 1, win 229, options [nop,nop,TS val 11296960 ecr 11296960], length 480: HTTP: GET /texture.jpg HTTP/1.0
02:04:27.368138 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0

# `GET /texture.jpg` response (server -> proxy -> client)
02:04:27.368303 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [P.], seq 1:238, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 237: HTTP: HTTP/1.1 200 OK
02:04:27.368355 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 238, win 237, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.369831 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], seq 238:7478, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 7240: HTTP
02:04:27.369893 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 7478, win 350, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.369925 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], seq 7478:14718, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 7240: HTTP
02:04:27.369933 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 14718, win 463, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.369933 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [P.], seq 1:4102, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 4101: HTTP: HTTP/1.1 200 OK
02:04:27.369953 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], seq 14718:16622, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 1904: HTTP
02:04:27.369953 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 4102, win 293, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.369964 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 16622, win 493, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.369988 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], seq 16622:26758, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 10136: HTTP
02:04:27.369988 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [.], seq 4102:11342, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 7240: HTTP
02:04:27.370002 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 26758, win 651, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370002 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 11342, win 406, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370021 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], seq 26758:36894, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 10136: HTTP
02:04:27.370029 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 36894, win 810, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370035 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [P.], seq 11342:12294, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 952: HTTP
02:04:27.370045 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [P.], seq 36894:45234, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 8340: HTTP
02:04:27.370045 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 12294, win 428, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370051 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 45234, win 940, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370060 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [P.], seq 12294:16390, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 4096: HTTP
02:04:27.370070 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 16390, win 492, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370121 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [F.], seq 45234, ack 481, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370142 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [.], seq 16390:26526, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 10136: HTTP
02:04:27.370156 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 26526, win 651, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370164 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [.], seq 26526:36662, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 10136: HTTP
02:04:27.370175 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 36662, win 809, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370184 IP b4f40c2642a2.80 > 172.21.0.1.53486: Flags [P.], seq 36662:45239, ack 379, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 8577: HTTP
02:04:27.370194 IP 172.21.0.1.53486 > b4f40c2642a2.80: Flags [.], ack 45239, win 943, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370216 IP b4f40c2642a2.54498 > play-nginx_http-server_1.play-nginx_default.80: Flags [F.], seq 481, ack 45235, win 940, options [nop,nop,TS val 11296960 ecr 11296960], length 0
02:04:27.370240 IP play-nginx_http-server_1.play-nginx_default.80 > b4f40c2642a2.54498: Flags [.], ack 482, win 235, options [nop,nop,TS val 11296960 ecr 11296960], length 0

clientとproxy間の通信は前と同様だが、proxyとserver間の通信がTCPのコネクションを使いまわしておらず、HTTPの通信ごとにTCPを新規接続、切断していることに注意したい。

最後に、 http://localhost:8082 に接続し、keepaliveの設定を行なったreverse proxyの通信を確認する。

# 3 way handshake (client <> proxy)
20:34:23.210464 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [S], seq 2333942496, win 29200, options [mss 1460,sackOK,TS val 12378507 ecr 0,nop,wscale 7], length 0
20:34:23.210523 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [S.], seq 1686264532, ack 2333942497, win 28960, options [mss 1460,sackOK,TS val 12378507 ecr 12378507,nop,wscale 7], length 0
20:34:23.210550 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 12378507 ecr 12378507], length 0

# `GET /` request (client -> proxy)
20:34:23.212127 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [P.], seq 1:411, ack 1, win 229, options [nop,nop,TS val 12378507 ecr 12378507], length 410: HTTP: GET / HTTP/1.1
20:34:23.212150 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [.], ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 0

# 3 way handshake (proxy <> server)
20:34:23.212569 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [S], seq 1304194196, win 29200, options [mss 1460,sackOK,TS val 12378507 ecr 0,nop,wscale 7], length 0
20:34:23.212610 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [S.], seq 149760532, ack 1304194197, win 28960, options [mss 1460,sackOK,TS val 12378507 ecr 12378507,nop,wscale 7], length 0
20:34:23.212632 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 12378507 ecr 12378507], length 0

# `GET /` request (proxy -> server)
20:34:23.212723 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 1:494, ack 1, win 229, options [nop,nop,TS val 12378507 ecr 12378507], length 493: HTTP: GET / HTTP/1.1
20:34:23.212749 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 0

# `GET /` response (server -> proxy -> client)
20:34:23.213029 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 1:242, ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 241: HTTP: HTTP/1.1 200 OK
20:34:23.213041 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 242, win 237, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213125 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 242:7482, ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 7240: HTTP
20:34:23.213140 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 7482, win 350, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213186 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 7482:14722, ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 7240: HTTP
20:34:23.213194 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [P.], seq 1:4097, ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 4096: HTTP: HTTP/1.1 200 OK
20:34:23.213199 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 14722, win 463, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213218 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 14722:16626, ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 1904: HTTP
20:34:23.213231 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 4097, win 293, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213239 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 16626, win 493, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213270 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 16626:18125, ack 494, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 1499: HTTP
20:34:23.213286 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [.], seq 4097:11337, ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 7240: HTTP
20:34:23.213282 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 18125, win 516, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213307 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 11337, win 406, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213320 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [P.], seq 11337:12289, ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 952: HTTP
20:34:23.213335 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 12289, win 428, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213362 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [P.], seq 12289:16385, ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 4096: HTTP
20:34:23.213377 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 16385, win 492, options [nop,nop,TS val 12378507 ecr 12378507], length 0
20:34:23.213410 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [P.], seq 16385:18125, ack 411, win 235, options [nop,nop,TS val 12378507 ecr 12378507], length 1740: HTTP
20:34:23.213426 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 18125, win 520, options [nop,nop,TS val 12378507 ecr 12378507], length 0

# `GET /stats.js` request (client -> proxy)
20:34:23.269929 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [P.], seq 411:750, ack 18125, win 520, options [nop,nop,TS val 12378513 ecr 12378507], length 339: HTTP: GET /stats.js HTTP/1.1

# `GET /stats.js` request (proxy -> server)
20:34:23.270248 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 494:916, ack 18125, win 516, options [nop,nop,TS val 12378513 ecr 12378507], length 422: HTTP: GET /stats.js HTTP/1.1

# `GET /stats.js` response (server -> proxy -> client)
20:34:23.270477 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 18125:18378, ack 916, win 243, options [nop,nop,TS val 12378513 ecr 12378513], length 253: HTTP: HTTP/1.1 200 OK
20:34:23.270518 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 18378, win 539, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.270580 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 18378:22481, ack 916, win 243, options [nop,nop,TS val 12378513 ecr 12378513], length 4103: HTTP
20:34:23.270595 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 22481, win 603, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.270733 IP e0433a29d961.80 > 172.21.0.1.44676: Flags [P.], seq 18125:22481, ack 750, win 243, options [nop,nop,TS val 12378513 ecr 12378513], length 4356: HTTP: HTTP/1.1 200 OK
20:34:23.270793 IP 172.21.0.1.44676 > e0433a29d961.80: Flags [.], ack 22481, win 588, options [nop,nop,TS val 12378513 ecr 12378513], length 0

# 3 way handshake (client <> proxy)
20:34:23.271120 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [S], seq 3882359748, win 29200, options [mss 1460,sackOK,TS val 12378513 ecr 0,nop,wscale 7], length 0
20:34:23.271154 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [S.], seq 3373201720, ack 3882359749, win 28960, options [mss 1460,sackOK,TS val 12378513 ecr 12378513,nop,wscale 7], length 0
20:34:23.271185 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 12378513 ecr 12378513], length 0

# `GET /texture.jpg` request (client -> proxy)
20:34:23.271364 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [P.], seq 1:379, ack 1, win 229, options [nop,nop,TS val 12378513 ecr 12378513], length 378: HTTP: GET /texture.jpg HTTP/1.1
20:34:23.271380 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [.], ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 0

# `GET /texture.jpg` request (proxy -> server)
20:34:23.271503 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [P.], seq 916:1377, ack 22481, win 603, options [nop,nop,TS val 12378513 ecr 12378513], length 461: HTTP: GET /texture.jpg HTTP/1.1

# `GET /texture.jpg` response (server -> proxy -> client)
20:34:23.271755 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 22481:22723, ack 1377, win 252, options [nop,nop,TS val 12378513 ecr 12378513], length 242: HTTP: HTTP/1.1 200 OK
20:34:23.271986 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 22723:32859, ack 1377, win 252, options [nop,nop,TS val 12378513 ecr 12378513], length 10136: HTTP
20:34:23.272007 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 32859, win 784, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272200 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [.], seq 1:7241, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 7240: HTTP: HTTP/1.1 200 OK
20:34:23.272275 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 7241, win 342, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272295 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [P.], seq 7241:8193, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 952: HTTP
20:34:23.272413 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 8193, win 364, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272613 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 32859:48787, ack 1377, win 252, options [nop,nop,TS val 12378513 ecr 12378513], length 15928: HTTP
20:34:23.272655 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 48787, win 1033, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272719 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [.], seq 48787:63683, ack 1377, win 252, options [nop,nop,TS val 12378513 ecr 12378513], length 14896: HTTP
20:34:23.272772 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [P.], seq 8193:16385, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 8192: HTTP
20:34:23.272735 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 63683, win 1266, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272816 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 16385, win 492, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272857 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [P.], seq 16385:24577, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 8192: HTTP
20:34:23.272857 IP play-nginx_http-server_1.play-nginx_default.80 > e0433a29d961.45688: Flags [P.], seq 63683:67719, ack 1377, win 252, options [nop,nop,TS val 12378513 ecr 12378513], length 4036: HTTP
20:34:23.272876 IP e0433a29d961.45688 > play-nginx_http-server_1.play-nginx_default.80: Flags [.], ack 67719, win 1329, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272876 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 24577, win 620, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272945 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [.], seq 24577:34713, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 10136: HTTP
20:34:23.272965 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 34713, win 779, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.272991 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [.], seq 34713:44849, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 10136: HTTP
20:34:23.273021 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 44849, win 937, options [nop,nop,TS val 12378513 ecr 12378513], length 0
20:34:23.273035 IP e0433a29d961.80 > 172.21.0.1.44680: Flags [P.], seq 44849:45239, ack 379, win 235, options [nop,nop,TS val 12378513 ecr 12378513], length 390: HTTP
20:34:23.273054 IP 172.21.0.1.44680 > e0433a29d961.80: Flags [.], ack 45239, win 960, options [nop,nop,TS val 12378513 ecr 12378513], length 0

proxyとserver間の通信が、一つのTCPコネクションだけを使って行われていることがわかる。 また、 /texture.jpg のリクエストは、clientとproxy間では新たなコネクションを作ってリクエストしていたが、proxyとserver間ではコネクションを再利用していることに注意したい。

Next.jsについて調べる

Next.jsを使っているのだが、内部的にどう動いているのかよくわかっていないので軽く調べてみた。

以下を使って、SSR処理時やルーティングでの挙動を見てみる。 https://github.com/zeit/next.js/tree/fc05c9c27307fd6910f7d87b8c65b085751ce190/examples/parameterized-routing

以下のような流れでサーバ起動まで実行できる。

$ npx create-next-app --example parameterized-routing parameterized-routing-app
$ cd parameterized-routing-app
$ npm run build
$ npm start

pagesディレクトリは以下のようなファイル構成になっている。

pages
├── blog.js
└── index.js

0 directories, 2 files

ビルド時に生成される .next は以下のようなディレクトリになっていた。

.next
├── BUILD_ID
├── build-manifest.json
├── bundles
│   └── pages
│       ├── _app.js
│       ├── _error.js
│       ├── blog.js
│       └── index.js
├── server
│   ├── bundles
│   │   └── pages
│   │       ├── _app.js
│   │       ├── _document.js
│   │       ├── _error.js
│   │       ├── blog.js
│   │       └── index.js
│   └── pages-manifest.json
└── static
    └── commons
        └── main-ee347f50f148624764ad.js

7 directories, 13 files

.next/bundles, .next/server/bundles の各ディレクトリに、pagesディレクトリのファイルに対応する blog.js, index.js が生成され、 _app.js, _error.js, _document.js も生成されている (_document.js はserverのみ)

_app.js, _error.js, _document.js は各ページのテンプレートのようなものであり、ソースコード上でカスタマイズもできる。今回は特にカスタマイズしていなかったが、出力としては個別のファイルに出たのだろう。 クライアント用のコードは圧縮されており、pages配下のディレクトリを、クライアント用とサーバ用でそれぞれ生成しているのだということが予想できる。(_document.js はサーバ側でしか実行されないため、サーバ用のコードしか吐かれていないのだろう)

npm start してから curl localhost:3000 すると、以下のようなHTMLが取れた。

<!DOCTYPE html><html><head><meta charSet="utf-8" class="next-head"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/index.js" as="script"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js" as="script"/><link rel="preload" href="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_error.js" as="script"/><link rel="preload" href="/_next/static/commons/main-ee347f50f148624764ad.js" as="script"/></head><body><div id="__next"><ul><li><a href="/blog/first">My first blog post</a></li><li><a href="/blog/second">My second blog post</a></li><li><a href="/blog/last">My last blog post</a></li></ul></div><div id="__next-error"></div><script>
          __NEXT_DATA__ = {"props":{"pageProps":{}},"page":"/","pathname":"/","query":{},"buildId":"8f79b495-de7d-47be-8e34-55823460818f","assetPrefix":"","nextExport":false,"err":null,"chunks":[]}
          module={}
          __NEXT_LOADED_PAGES__ = []
          __NEXT_LOADED_CHUNKS__ = []

          __NEXT_REGISTER_PAGE = function (route, fn) {
            __NEXT_LOADED_PAGES__.push({ route: route, fn: fn })
          }

          __NEXT_REGISTER_CHUNK = function (chunkName, fn) {
            __NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
          }

          false
        </script><script async="" id="__NEXT_PAGE__/" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/index.js"></script><script async="" id="__NEXT_PAGE__/_app" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js"></script><script async="" id="__NEXT_PAGE__/_error" src="/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_error.js"></script><script src="/_next/static/commons/main-ee347f50f148624764ad.js" async=""></script></body></html>%                                                  [

グローバル変数を用いて、必要なデータをObjectとして渡しているのがわかる。 他の処理はhistory関連の処理だろうか?

気になるのは _next/8f79b495-... みたいなファイルをダウンロードしようとしているところだ。 .nextディレクトリと一対一で対応していない。 8f79b495-... の値は .next/BUILD_ID と同じもののようだ。これを見てNext.jsが動的にルーティングしているのだろうか。 また、各jsファイルはbundleディレクトリ配下のものと同じものっぽい。

$ cat .next/bundles/pages/_app.js | md5
9f6ae7fa86f03007f1ffd9b31e5581d6
$ curl http://localhost:3000/_next/8f79b495-de7d-47be-8e34-55823460818f/page/_app.js | md5
9f6ae7fa86f03007f1ffd9b31e5581d6

以上の処理を見ると、レンダリング結果のHTMLを返しつつも、クライアント側に必要なjsファイルは非同期でダウンロードしているようだ。 また、別のページとなる blog.js はダウンロードしていないということにも注意したい。

次は、ブラウザからlocalhost:3000にアクセスし、画面遷移を行なってみる。

リンクをクリックして画面遷移すると、 blog.js だけが新たにダウンロードされて画面遷移が行われた。 ここはSPAの挙動となり、スムーズな画面遷移が行えるようになっている。