Echoで始める憧れのOSS活動への第0.1歩
はじめに
3年目のバックエンドエンジニアです
OSS活動についていつかはやってみたいなと思っていたものの、
なかなかハードルが高そうで躊躇していました
今回ソフトウェアデザイン 2022年9月号を読んで大変刺激を受けたので、
小さい一歩から始めてみようと思い実践してみました
この記事でやること
-
ソフトウェアデザイン 2022年9月号OSSソースコードリーディングのススメの内容の実践
- 適宜開発を進めながら、都度必要な箇所のソースコードを読んで見る
- 使用言語はGo(Echo)
動作環境
- macOS: BigSur(11.6.1)
- Go: 1.18.4 darwin/amd64
- Echo: 4.9.0
- VSCode: 1.71.2
OSSを読む目的を決める
まずOSSを読む目的を明確にします
自分の場合、
- Go言語及びフレームワークの理解を深めたい
- 将来的にOSS活動を通じて海外就職してみたい
という2点が目的になります
読みたいOSSのコードを決める
次に読みたいOSSのコードを決めます
ソースコードを選ぶ基準として、下記のような項目が挙げられていました
- 使い慣れているもの
- 仕様を把握しているもの(例えばHTTPの仕様など)
- なるべく小さくてシンプルなもの
- よく使われているプログラミング言語で書かれているもの
自分の場合、
- Go言語で簡単なアプリケーションを作成したことがある
- HTTPの仕様についてある程度は理解している
- Webフレームワークを使用した経験がある
といった理由から、
GoのWebフレームワークであるEchoを選択してみました
まずはドキュメントを読む
開発をはじめるにあたり、まずはドキュメントを読んでみます
Echoの公式ドキュメントはこちらになります
クイックスタート
まずは下記のコマンドを実行し、Echoをインストールしていきます
$ mkdir myapp && cd myapp
$ go mod init myapp
$ go get github.com/labstack/echo/v4
インストールが完了したら、mainとなるファイルを作成してきます
touch server.go
作成したファイルに、下記を追記します
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
Echoを起動し、動作を確認してみます
go run server.go
// 実行結果
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:1323
ブラウザを起動し、 http://localhost:1323/ にアクセスしてみます
Hello, World!が表示されました
シンプルな記載でWebサーバを立ち上げることができました
ソースコードを読んで見る
ここからが本題になります
まず、main関数の内容について確認してきます
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
内容としては、下記の3つになります
- echo.New() でEchoのインスタンス作成
- e.GET(パス, ハンドラ) でルーティング
- e.Logger.Fatal(e.Start(":1323")) でログ出力
今までの自分の場合、
「Echoシンプルだし便利だ!別のハンドラーを追加してみよう!」
と開発を進めてしまっていましたが、一旦立ち止まってソースコードを読んでみたいと思います
※思ったより記事が長くなってしまったので、上記1.の部分のみ記事にします
Echoインスタンスの作成
まずはEchoのインスタンスを作成している部分のソースを見てみたいと思います
echo.New()を選択した状態でF12、または ⌘ + クリックしてみます
echo.New()の中身は下記のようなコードになっていました
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{
filesystem: createFilesystem(),
Server: new(http.Server),
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
Logger: log.New("echo"),
colorer: color.New(),
maxParam: new(int),
ListenerNetwork: "tcp",
}
e.Server.Handler = e
e.TLSServer.Handler = e
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{}
e.JSONSerializer = &DefaultJSONSerializer{}
e.Logger.SetLevel(log.ERROR)
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
e.pool.New = func() interface{} {
return e.NewContext(nil, nil)
}
e.router = NewRouter(e)
e.routers = map[string]*Router{}
return
}
echo.New()の中身は、ざっくりと下記の内容が行われてました
- ポイント型の Echo の構造体を初期化
- Echoの構造体の項目に値を設定
- ポイント型のEchoを戻り値として返却
上記の内、1と3はなにが行われているか理解できましたが、
2の部分がいまいちイメージできていません
ということで、
続いてEchoの構造体をソースコードを確認していきます
すべてのソースコードを読むとかなり時間がかかってしまうため、
今回は
e.Server.Handler = e
の部分のみに着目して確認していきます
Echo構造体の中身の確認
Echoの構造体の中身は下記の通りです
type (
// Echo is the top-level framework instance.
Echo struct {
filesystem
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
startupMutex sync.RWMutex
StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
router *Router
routers map[string]*Router
pool sync.Pool
Server *http.Server
TLSServer *http.Server
Listener net.Listener
TLSListener net.Listener
AutoTLSManager autocert.Manager
DisableHTTP2 bool
Debug bool
HideBanner bool
HidePort bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
JSONSerializer JSONSerializer
Validator Validator
Renderer Renderer
Logger Logger
IPExtractor IPExtractor
ListenerNetwork string
}
// 以下省略
)
上記より
e.Server.Handler = e
はGoの標準パッケージである net/http のServerのHandlerということがわかりました
net/httpのServer構造体の確認
Server.Handlerの内容は下記のとおりです
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
// 省略
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// 省略
}
さらに、Handlerは
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeHTTP()をメソッドに持つインターフェースであることがわかりました
Handlerインターフェースを満たす実装部分の確認
echo.goに戻り、ソースコードを確認すると下記の通りServeHTTP()を実装しているため、
Handlerインターフェースを満たしています
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
var h func(Context) error
if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h = c.Handler()
h = applyMiddleware(h, e.middleware...)
} else {
h = func(c Context) error {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
h := c.Handler()
h = applyMiddleware(h, e.middleware...)
return h(c)
}
h = applyMiddleware(h, e.premiddleware...)
}
// Execute chain
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
// Release context
e.pool.Put(c)
}
まとめ
少し長くなってしまいましたが、
e.Server.Handler = e
の部分でなにをしているのか、改めて整理してみたいと思います
- 左辺の e.Server.Handler はServeHTTP()をメソッドに持つインターフェース(を宣言した変数)
- 右辺の e は ポインタ型のEchoであり、ServeHTTP()を実装している
→ ポインタ型のEchoはHandlerインターフェースに含まれる(満たしている)
つまり、e.Server.Handler = e とすることで
Handlerインターフェースを満たすことになり、
Handlerインターフェースを引数に持っている関数も実行することができるようになります
その結果、Echo経由でGoのnet/httpなどの機能が使えるようになると理解しました
おわりに
憧れのOSS活動への第0.1歩として、GoのフレームワークであるEchoのソースコードを読んでみました
ソースコードを読む中で、Goのインターフェース周りの理解が浅いことに気がつくことができました
また、時間はかかったものの、
単に「こう書けば動く」という認識だったものを
「なぜそう書くことで動くのか?」という納得感につなげることができました
OSS 活動への0.2歩、0.3歩へとつなげていけるよう、
理解が浅い部分について復習していきたいと思います
参考
参考文献
武内 覚(2022) コードリーディングの始め方 Software Design編集部 『ソフトウェアデザイン 2022年9月号』 技術評論社 pp.176-184
早川 大貴(2022) コードリーディングの具体的手法 Software Design編集部 『ソフトウェアデザイン 2022年9月号』 技術評論社 pp.186-199
参考サイト
Echo公式ドキュメント
net/http/server.go
Interfaces in GoLang
Goのinterfaceがわからない人へ
Discussion