TechTrain
ログイン
  • MISSION
    MISSION
  • FAQ
    Frequently Asked Questions
  • メンター
    Mentor
  • お問い合わせ
    Contact
利用規約 プライバシポリシー メンターログイン
TechTrain
MISSION
MISSION
  • MISSION
  • 実践 Web アプリケーション入門 〜便利な仕組みを実装しよう〜

実践 Web アプリケーション入門 〜便利な仕組みを実装しよう〜

主森 理

推奨スキル

  • Go
ログインしてください

MISSIONについて

About the MISSION

要件定義

この MISSION は、Go の Railway の題材を用いて行うため Railway 未クリアの人は是非 Railway にもチャレンジしてみてください。 Go の Railway では利用者向けに TODO 機能を実装してもらいましたが、この MISSION では Web アプリケーションを運用していく上で開発者向けの便利な仕組みを幾つか実装してもらいます。 実装する項目 - Panic が発生してもアプリケーションが動き続ける仕組みの実装 - HTTP Request 毎の個別の値を伝播し、後続の処理で利用する仕組みの実装 - API の処理前後で任意のロジックを動かす仕組みの実装 - [Extra Stage] Web アプリケーションの安全な終了処理の実装

学べること

- Middleware の設計理解と実装方法 - Context パッケージを利用方法とHTTP Request 毎の固有値の伝播 - [Extra Stage] Web アプリケーションの安全に終了する方法 (Graceful Shutdown) と Go の便利な仕組み

MISSIONを進める上でのヒント

- 新しいアイデアを捻り出す必要はありません、GitHub 上に同じようなコードがたくさんあります。 - コピペするのは構いませんが、Accountability を持ってコピペしないとメンターとの面談で困ることが想定されます。 - 要件として課題を出していますが、課題を進めていく中で思いついた機能は積極的に実装して面談でメンターにアドバイスを貰ってみてください。

MISSIONコンプリートまでのステップ

Mission STEPs
1
STEP 1
Panic を Recover する Middleware を実装してみよう。
2
CHECKPOINT
チェックポイント面談をしよう
3
STEP 2
Context にアクセス元の device の OS 名を格納する Middleware を実装し
4
STEP 3
Access log を出力する Middleware を実装してみよう。
5
CHECKPOINT
チェックポイント面談をしよう
6
STEP 4
Basic 認証を導入してみよう
7
STEP 5
Basic 認証の検証作業をしてみよう
8
CHECKPOINT
チェックポイント面談をしよう
9
STEP 6
HTTP サーバーを安全に終了させよう。
10
COMPLETE
コンプリート面談をしよう
STEP 1

Panic を Recover する Middleware を実装してみよう。

4h~8h
APIを複数実装してると任意のグループ単位で共通した処理を書く必要が出てきます。 その場合は、Middlewareといわれる実装方法を用いることで、共通処理を1か所にまとめることができます。(なぜ1か所にまとめたほうが良いかは、Don't Repeat Yourself で調べてみよう。) このMISSIONでは、呼び出されたAPI処理中にpanicが発生したときにrecoverするMiddlewareを実装してみよう。 1. handler ディレクトリの下に middleware ディレクトリを作成し、その中に recovery.go というファイルを作成しよう。recovery.go の中に下記のコードをコピーしよう。 ```go package middleware import "net/http" func Recovery(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { // TODO: ここに実装をする } return http.HandlerFunc(fn) } ``` 2. TODO コメントがあるところに実装をしてみよう。実装する内容は、defer を使って panic が起きたときに recover をしアプリケーションが止まらないようにするする実装と、引数で受け取っている h から ServeHTTP を呼び出して HTTP Request を連鎖 (chain) させる実装です。 3. 次に必ず呼び出すと panic する http.Handler インタフェースを満たす実装を行い、`/do-panic` という path で mux に作成した Handler を登録しよう。その後、`go run main.go` を実行し、`/do-panic` にアクセスするとアプリケーションが panic を起こして停止することを確認しよう。 4. 最後に 3. で mux に登録した Handler を middleware.Recovery に置き換えて、その引数として設定しよう。その後、`go run main.go` を実行し、`/do-panic` にアクセスすると panic が起きるがアプリケーションが停止していないことを確認しよう。 見るポイント - http.Hnadler インタフェースを理解して使っているか - それぞれの Handler が呼ばれるタイミングを理解しているか - defer を上手く利用できているか、defer で呼び出している即時関数が適切かどうか(変数のスコープなど) - メンテナビリティが考慮されているか(テストのしやすさ、コードの再利用性)
CHECKPOINT INTERVIEW

チェックポイント面談をしよう

メンターに面談予約し、チェックポイント面談を行いましょう。
STEP 2

Context にアクセス元の device の OS 名を格納する Middleware を実装し

4h~8h
Middleware 機構を使って、HTTP Request からアクセス元の device の OS を解析して、Context に格納してみよう。 Go 言語で作る Web アプリケーションでは、HTTP Request 単位で Context にデータを格納して受け渡して行く方法が一般的に取られることが多いです。 今回の処理をすることで、ビジネスロジックで OS 毎に処理を切り分けるなどの処理が可能になります。 1. まずは、HTTP Request から OS 名を取得するための、外部ライブラリを go get コマンドを使って取得しよう。今回は、このライブラリを使おう => https://github.com/mileusna/useragent 2. ライブラリの README や GoDoc を読んで使い方を把握しよう。User-Agent の文字列は、http.Request#UserAgent() で取得することができる。 https://pkg.go.dev/net/http#Request.UserAgent 3. http.Request から Context を取り出して、OS 名を格納しよう。格納した後は、再び http.Request に Context を登録しよう。Context への値の格納は、GoDoc に注意点が記述されています。 https://pkg.go.dev/context#WithValue
STEP 3

Access log を出力する Middleware を実装してみよう。

4h~8h
次にサーバにアクセスしてきたリクエストの情報をリアルタイムに標準出力に書き出そう。 ある、handler の処理の前に加えて、後にも処理が必要になってきます。これも Middleware 機構を使うことで実現できます。 1. handler の処理を行う前にアクセス日時を任意の変数に代入しよう。その後、handler を呼び出して処理を続行させよう。 2. handler の処理が終了後に 1. で取得しているアクセス日時と処理後の日時の差分(処理時間)を計算しよう。 3.任意の構造体に time.Time 型の Timestamp, int64 型の Latency, string 型の Path, string 型の OS をフィールド定義しよう。 4. それぞれのフィールドを埋めて、json にして標準出力に書き出そう。Timestamp はアクセス日時、Latency は処理時間 (単位はミリ秒)、Path は http.Request の URL から取得、OS は 2. で実装した context から取り出そう。標準出力への書き出しは、fmt.Println 関数を使うことで実現できます。 見るポイント - 2 で実装した context の扱い方が適切か(Key をユーザ定義してるかなど) - next handler の呼び出し前後の処理が適切かどうか - Middlware の呼び出す順番が適切か
CHECKPOINT INTERVIEW

チェックポイント面談をしよう

メンターに面談予約し、チェックポイント面談を行いましょう。
STEP 4

Basic 認証を導入してみよう

4h~8h
Basic 認証 (https://ja.wikipedia.org/wiki/Basic%E8%AA%8D%E8%A8%BC) を導入して、User ID と Password を知っている人しかアクセスできないようにしよう。 1. 1. User ID と Password は、アプリケーションが起動するときに環境変数から取得し設定できるようにしよう。コード内に直接書くとパスワードが平文で分かってしまうので、環境変数を利用して設定を行おう。それぞれ User ID と Password を受け渡す環境変数の名前は、ID が `BASIC_AUTH_USER_ID` 、Password が `BASIC_AUTH_PASSWORD` としよう。環境変数からの値の取得の仕方や、起動時に環境変数の渡し方はこの記事を参考にしよう。 https://www.spinute.org/go-by-example/environment-variables.html 2. いままでと同じく Middleware を作ろう、アクセスを制限したい Handler の前に http.Request#BasicAuth メソッドを呼び出して、User ID と Password を照合しよう。 https://pkg.go.dev/net/http#Request.BasicAuth 3. 環境変数から取得した User ID, Password が正しい場合は処理を続行しよう、それ以外の場合は HTTP Status Code を 401 にし、処理を終了しよう。
STEP 5

Basic 認証の検証作業をしてみよう

4h~8h
4 で実装したアプリケーションを起動させて、ブラウザから挙動を実際に検証してみよう。 一般的に検証作業では、正常系と異常系の2パターンがあり。どちらも網羅することで意図した挙動になっているか確認します。 検証項目は仕様に沿って定義されていきます。 大体、異常系は正常系のパターンの倍ぐらいか、それ以上になることが多いです。(少ない場合は異常系を網羅できているか、実装を確認する目安にもなります) https://tex2e.github.io/rfc-translater/html/rfc7617.html 検証作業確認項目 正常系 - 対象の API のみ Basic 認証がかかっているか、どうか。 - 正しい User ID, Password で Basic 認証をクリアしアクセスできるかどうか。 異常系 - 間違った User ID, Password を送信した場合、 Basic 認証が失敗しHTTP Status Code が 401 で返却されているかどうか。 - 空の User ID, Password を送信した場合、 Basic 認証が失敗し HTTP Status Code が 401 で返却されているかどうか。 - アクセス時に User ID, Password を送信しなかった場合、Basic 認証が失敗し HTTP Status Code が 401 で返却されているかどうか。 見るポイント - 4 で実装している Basic Auth が問題文の要件を満たしているか - 環境変数の渡し方、読み取り方 - 認証処理を行うタイミング - エラーハンドリングが適切か - 検証作業が整理されて実施されているか - 実施項目、入力項目、出力項目、判定、etc.
CHECKPOINT INTERVIEW

チェックポイント面談をしよう

メンターに面談予約し、チェックポイント面談を行いましょう。
STEP 6

HTTP サーバーを安全に終了させよう。

4h~8h
Goで実装しているアプリケーションが起動しているときに外部から終了や割り込みの命令 (signal) を受けるときがあります。 これらの命令を受け即時終了してしまうと、リクエスト処理が強制的に終了され中途半端な状態の処理が発生する恐れがあります。 そのためそれらの命令を受け取った場合は、実行中の処理の終了を待つことなど適切な終了処理を行う必要があります。これを、一般的には Graceful Shutdown といいます。 この課題は Go 言語の仕様や、HTTP Server の挙動を理解していないとクリアできない難しめの難易度になっています。分からないところは、検索して調べたり、それでも分からない場合は質問事項をまとめてメンターと相談してみましょう。 利用する標準パッケージの実装: signal.NotifyContext, http.Server, sync.WaitGroup 標準パッケージのみで実装できますが、難しい場合は下記のライブラリを利用して実装してみよう。 https://github.com/110y/run 見るポイント - コード可読性 - signal.NotifyContext の signal の引数 (os.Interrupt, os.Kill) - WaitGroup (もしくは、それに準ずる処理) のハンドリング - signal の受け取り処理 (signal 受け取り後、timeout の設定、shutdown 呼び出し) - shutdown, listen and serve それぞれのエラーハンドリング
COMPLETE INTERVIEW

コンプリート面談をしよう

メンターに面談予約し、MISSIONコンプリートのジャッジをしてもらいましょう。MISSION詳細下部「おすすめのメンター」からランダムで予約してください。

MISSIONの担当者

主森 理

Message

この MISSION は、当初 Go の Railway に入る想定でしたが別の単元として切り出すことで、より STEP by STEP で Go を学べると思い MISSION という形になりました。Go の Railway では、具体的なアプリケーションとして TODO 機能の実装を熟してもらいましたが、この MISSION で Focus しているのは便利な仕組みです。 愚直に実装するのも構わないのですが、仕組み化を行うことで再利用性や、メンテナビリティ向上、テストコード量の低減など多くの恩恵を受けることができます。また、コードを書けば書くほどバグが生まれる可能性が向上するので、シンプルにすることでバグ発見の手間も低く抑えることができます。 ぜひ、この MISSION をクリアして仕組み化の恩恵を感じてみてください。