Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server_generatorのControllerに渡されるContextがRequestのものでない #29

Closed
tsuzu opened this issue Jul 2, 2020 · 4 comments · Fixed by #58
Closed

server_generatorのControllerに渡されるContextがRequestのものでない #29

tsuzu opened this issue Jul 2, 2020 · 4 comments · Fixed by #58

Comments

@tsuzu
Copy link
Collaborator

tsuzu commented Jul 2, 2020

生成されたControllerの第一引数がContextとなっているが、これはRequestからきたものではなく初期化時のものなので、わかりづらい

だが破壊的変更になるのでどうするべきか難しい

@micheam
Copy link
Contributor

micheam commented Aug 26, 2020

利用者がどのコンテキストを使うべきか。を悩まない様なシグネチャに変更したい。

func (p *PostCreateTableController) PostCreateTable(
ctx context.Context, c echo.Context, req *PostCreateTableRequest,
) (res *PostCreateTableResponse, err error) {

ctx context.Contextc.Request().Context() のどちらを使うのが正しいんだろう 🤔

補足

現状では、Handler Func にて引数から受領する context.Context が2種類存在している。

(1) 第1引数にて引き渡される ctx context.Context
(2) 第2引数 c echo.Context の内部に保持されている http.Request を経由して得られる context.Context (c.Request().Context())

(1) は server 開始時に生成された グローバルなコンテキストである一方で、 echo.Context.Request().Context() で取得できるコンテキストは リクエスト毎に生成されたものとなる。 net/http/#Request.Context

ライブラリ の利用者が誤って、 c.Request().Context() を使うと、server 開始時に設定されたグローバルな設定値へのアクセスが失われる。一方で、 context.Context の本来の利用方針からすると、リクエスト毎にコンテキストを切りたい気がする。

A Context carries a deadline, cancelation signal, and request-scoped values across API boundaries
https://blog.golang.org/context より

@micheam
Copy link
Contributor

micheam commented Aug 27, 2020

一応、思いついた対応案をメモしておきます。
(別に無視していただいて構わない内容です)

案1: 独自Contextの提供

echo.Context と context.Context はどちらも interface であるため、
双方を実装した独自型を提供することで透過的に使用することができる。

// EchoContext is a type alias for echo.Context
type EchoContext echo.Context

// APIContext implements conetxt.Context and echo.Context
type APIContext struct {
	context.Context
	EchoContext       // <-- 別名にしないと、コンパイラに怒られる
}
package xxxx

func (g *GetXXXXController) GetXXXX(
	ctx *interfaces.APIContext, // <== 独自型に変更
	req *GetXXXXRequest,
) (res *GetXXXXResponse, err error) {

	// ctx *interfaces.APIContext は context.Context interface を満たしているので、
	// context 配下の関数にそのまま引き渡すことができる 
	childCtx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
	defer cancel() // releases resources if slowOperation completes before timeout elapses
	res, err = slowOperation(childCtx)
	// ....
}

※ 実際は 各Routesで使うためには interfaces パッケージでは実現できない(循環参照になる)ため、実装するパッケージは検討が必要そう。

Bootstrap 経由で 各種 Router に引き渡された context.Context と、
echo.Context を元に独自コンテキストを生成して Handler 関数を実行する様にすれば、
利用者はそれをそのまま context.Context としても echo.Context としても
使用できる。

func (r *Routes) GetEvents(ctx context.Context) echo.HandlerFunc {
	i := NewGetEventsController()
	return func(c echo.Context) error {
		req := new(GetEventsRequest)
		if err := c.Bind(req); err != nil {
			return c.JSON(http.StatusBadRequest, map[string]interface{}{
				"code":    http.StatusBadRequest,
				"message": "invalid request.",
			})
		}

		res, err := i.GetEvents(&interfaces.APIContext{
			Context:     ctx,
			EchoContext: c,
		}, req)
		if err != nil {
			return err
		}
		if res == nil {
			return nil
		}

		return c.JSON(http.StatusOK, res)
	}
}

labstack/echo の各種ドキュメントやIssueをみると、
この方法が推奨されている節がある?

案2: Request.Context に詰める

ハンドラ関数の引数から ctx context.Context を削除し、代わりに echo.Context.Request().Context() 経由で
取得する仕様とする。

package xxxx

func (g *GetXXXXController) GetXXXX(
	c echo.Context, // <== ctx context.Context は引数から削除
	req *GetXXXXRequest,
) (res *GetXXXXResponse, err error) {
	
	ctx := c.Request().Context()
	childCtx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
	defer cancel() // releases resources if slowOperation completes before timeout elapses
	res, err = slowOperation(childCtx)
	// ....
}

Bootstrap 経由で 引き渡された context.Context がハンドラ関数内部で参照できるように、
RoutesでRequestの内部に設定しておく感じ。

func (r *Routes) GetEvents(ctx context.Context) echo.HandlerFunc {
	i := NewGetEventsController()
	return func(c echo.Context) error {
		req := new(GetEventsRequest)
		if err := c.Bind(req); err != nil {
			return c.JSON(http.StatusBadRequest, map[string]interface{}{
				"code":    http.StatusBadRequest,
				"message": "invalid request.",
			})
		}
		c.SetRequest(c.Request().WithContext(ctx)) // <== Request にコンテキストをつめてあげる
		res, err := i.GetEvents(c, req)
		if err != nil {
			return err
		}
		if res == nil {
			return nil
		}

		return c.JSON(http.StatusOK, res)
	}
}

@tsuzu
Copy link
Collaborator Author

tsuzu commented Aug 27, 2020

個人的にはDIをしたい訳でもなく毎度メソッドの引数に渡す意味はないのでController側で持って仕舞えば良いと思っています(Contextではなく何らかのstructとして)

@moezakura moezakura linked a pull request Sep 28, 2020 that will close this issue
@54m
Copy link
Member

54m commented Sep 28, 2020

already merged

@54m 54m closed this as completed Sep 28, 2020
@tsuzu tsuzu moved this from Todo to Done in 1.0.0 Sep 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
1.0.0
Done
Development

Successfully merging a pull request may close this issue.

4 participants