gorilla/sessions と gorilla/context と net/http.Request.WithContext で気をつけること

net/http には net/http.Request.WithContext という context を紐付け直すメソッドがある。

実装はこうなっている。

func (r *Request) WithContext(ctx context.Context) *Request {
      if ctx == nil {
          panic("nil context")
      }
      r2 := new(Request)
      *r2 = *r
      r2.ctx = ctx
  
      // Deep copy the URL because it isn't
      // a map and the URL is mutable by users
      // of WithContext.
      if r.URL != nil {
          r2URL := new(url.URL)
          *r2URL = *r.URL
          r2.URL = r2URL
      }
  
      return r2
  }

ところで、session を扱おうとすると gorilla/sessions というのが出てきたりする。 echo*1 とか gin*2 のコミュニティ?の session ライブラリでも使われたりする。のでまだメジャなライブラリと言えるかもしれない。

gorilla/sessions は gorilla/context という標準の context package が出てくる前に生まれた request scope な値を入れるためのライブラリに依存している。 これは map[*http.Request]map[interface{}]interface{} という map で管理するというもの。

また

Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler or else you will leak memory! An easy way to do this is to wrap the top-level mux when calling http.ListenAndServe:

という記述があり、要は最後にこの map から request object を消去しないとメモリリークするという話。

ただ、ここで上の Request.WithContext の実装見てわかるのだがポインタが変わるので、gorilla/context の map に入った request object を消すことが出来ない。 なので、context.ClearHandler を使ってもメモリリークする。

echo とか gin にはライブラリの context object があってそこに request やメタデータを入れられるのでそちらで管理している限りは request object のポインタは変わらず、最後に context.ClearHandler を呼べば無事 map から消去出来る。

もし Request.WithContext を使いたいのであれば gorilla/context の map に request object を入れなければ良い。 gorilla/sessions の store interface には GetNew (https://godoc.org/github.com/gorilla/sessions#Store) という method があり、Get は gorilla/context を利用してしまうので New の方を呼び、その session object 自体を Request.WithContext で紐づけてあげれば良い。

結論

gorilla/sessions v2 頑張ってほしい。

github.com