第1回 ゴリラ.vimが開催されました
2/18(月)にゴリラ.vim #1が開催され、スタッフ兼発表者として参加してきました。
スタッフになったのはvim-jpのslackでゴリラさんこと@gorilla0513さんがVimの勉強会をやろうとしているのを見かけ、これは前々から思っていた渋谷でVim勉強会をするチャンスなのでは!?と会場提供を申し出たのがきっかけです。
そして当初は発表するネタが思い浮かばなかったのでスタッフに専念するつもりでいたのですが、少し経っても発表者枠が空いており、かつメンバーがVimConf 2018のスピーカーばかりという状態だったため、これは自分も何か話した方が良いんじゃないかと考え、ここ最近使ったgotypeとgo-treeを基にVimと外部コマンドという発表をしてきました。
ちなみに発表・デモは@thincaさんのshowtimeとquickrunを使いました。
全体の発表内容としては、短い時間ながらも初心者向けから上級者向けまであり、とても充実したものとなっていました。
特に前半の発表は自分にもそういう時期があったんだよなぁ、と非常に懐かしい気持ちになりました。
その後、一通り発表が終わった後は懇親会となり、各々がVimトークでとても盛り上がっていました。
渋谷ヒカリエ 弊社セミナールームにて ゴリラ.Vimが開催されました。懇親会では神々によるvimrcの設定サポートなど、とても和気あいあいとした勉強会でした。開催に協力することができて大変光栄です :)#gorillavim #denatechstudio pic.twitter.com/8u6mFUjACM
— DeNA Tech (@DeNAxTech) 2019年2月18日
開催前からこのイベントは盛り上がる...という予感はありましたが、予想以上に盛り上がり、スタッフとしても発表者としても参加者としても、嬉しい限りです。
トレンド入り!!!!!
— ゴリラ (@gorilla0513) February 18, 2019
#gorillavim pic.twitter.com/UTUbpFpGXv
ということで、今回ご参加いただいた皆様、ありがとうございました。
次回も是非よろしくお願いします。
(#2は少し枠が増えています!)
Vimの折畳に対応したtreeコマンド
golang.tokyo #21のDevQuizはGoでtreeコマンドを作成するというものでした。
treeコマンドといえば、以前Vimでファイル一覧をツリー表示するためのtree.vimというプラグインを作る時に使いました。
このプラグインはディレクトリにマーカーをつけることでサブディレクトリを階層ごとに折り畳んで表示します。
当時、マーカーを(Go製の)自作コマンドでつけることも検討しましたが、実装が非常に面倒くさそうだったのでVim scriptでtreeコマンドの結果をパースすることにしました。
そんな事情があったため、せっかくなのでDevQuizをやりつつ、追加機能としてVimの折畳に対応した出力形式も実装してしまおう!と思って作ったのがgo-treeです。*1
こちらはコマンドラインフラグに-V
をつけると以下のような出力になります。
$ go-tree -V github.com/daisuzu/tree.vim github.com/daisuzu/tree.vim/../ github.com/daisuzu/tree.vim/./ github.com/daisuzu/tree.vim/README.md github.com/daisuzu/tree.vim/autoload/{{{ github.com/daisuzu/tree.vim/autoload/tree.vim}}} github.com/daisuzu/tree.vim/ftplugin/{{{ github.com/daisuzu/tree.vim/ftplugin/tree.vim}}} github.com/daisuzu/tree.vim/plugin/{{{ github.com/daisuzu/tree.vim/plugin/tree.vim}}} github.com/daisuzu/tree.vim/syntax/{{{ github.com/daisuzu/tree.vim/syntax/tree.vim}}}
コマンド自体はVimから実行しても良いし、
:r! go-tree -V [DIR]
シェルで実行した結果をVimに流し込んでもOKです。
# -cでfoldmethod(fdm)をmarkerにする go-tree -V [DIR] | vim - -R -c 'setl fdm=marker' # モードラインでfoldmethod(fdm)をmarkerにする echo -e "vim: fdm=marker\n$(go-tree -V [DIR])" | vim - -R
そうするとgithub.com/daisuzu/tree.vim
はVimで以下のように表示されます。
github.com/daisuzu/tree.vim/../ github.com/daisuzu/tree.vim/./ github.com/daisuzu/tree.vim/README.md +-- 2 lines: github.com/daisuzu/tree.vim/autoload/----------------------------- +-- 2 lines: github.com/daisuzu/tree.vim/ftplugin/----------------------------- +-- 2 lines: github.com/daisuzu/tree.vim/plugin/------------------------------- +-- 2 lines: github.com/daisuzu/tree.vim/syntax/------------------------------- ~ ~ ~ ~
さらに、コマンド単体で動作するのでjob機能を使った非同期実行も簡単です。
:vnew | setl fdm=marker | call job_start('go-tree -V', {'out_io': 'buffer', 'out_buf': bufnr('%')})
このように、github.com
配下に大量のファイルがあるような場合でもVimをブロックすることなく操作を継続することができます。
現在はvimrcにAtree
コマンドを定義してgo-treeを常用しています。
filetypeをtree.vim
のものにすることで、そのまま表示するよりも少し見栄えが良くなっています。
command! -nargs=? -complete=dir -count -bang -bar ATree \ call s:async_tree(<q-args>, <count>, <bang>0, <q-mods>) function! s:async_tree(dir, depth, bang, mods) abort let cmd = 'go-tree -V ' . g:tree_options if a:bang " コマンドに!をつけたら隠しファイルも表示する let cmd .= ' -a' endif if a:depth > 0 let cmd .= ' -L ' . a:depth endif " cd後もgfできるようにディレクトリ名を絶対パスにする let cmd .= ' ' . fnamemodify(a:dir != '' ? a:dir : '.', ':p:h') execute a:mods . ' new' setfiletype tree let bufnr = bufnr('%') call job_start(cmd, { \ 'out_io': 'buffer', \ 'out_buf': bufnr, \ 'exit_cb': {channel, msg -> s:goto_first(bufnr)}, \ }) endfunction " オリジナル版はカーソル位置が先頭行になるので同じ挙動にする function! s:goto_first(bufnr) if bufnr('%') != a:bufnr " バッファを移動していたら何もしない return endif if line('.') != line('$') " 行を移動していたら何もしない return endif normal gg endfunction
予想通り実装は面倒でしたが、Vimをより快適に使えるようになりました!
*1:人が見ることはあまり考えてませんでした...
Vimのexecute()と組み合わせて便利なコマンド
この記事はVim Advent Calendar 2018の13日目の記事です。
先月に開催されたVimConf 2018で、Migrating plugins to standard featuresというタイトルで発表してコマンドの結果をバッファに表示する方法を紹介しました。
その中で解説しきれなかったテクニックとして、以下のようなコマンドを定義しておくとL <コマンド>
*1のようにして簡単に任意のコマンドの結果をバッファに表示することが出来るようになります。
command! -bar ToScratch \ setlocal buftype=nofile bufhidden=hide noswapfile command! -nargs=1 -complete=command L \ <mods> new | ToScratch | \ call setline(1, split(execute(<q-args>), '\n'))
当日はプラグイン一覧やMRUを表示する用途として、
:scriptnames
v:oldfiles
(:oldfiles
でも良い)
を紹介しましたが、簡単に使えるなら他にも便利なコマンドがあるんじゃないかと思って調べてみました。
なお、対象は:exusage
から
- 引数不要
- ファイルタイプやカーソル位置、バッファの状態などに依存しない
- 結果が複数行になる
コマンドのみとしています。(収拾ががつかなくなりそうなので...)
定義系
各種定義を一覧表示するのは何かと便利そうです。
年末にvimrcを掃除する際などにも大活躍するんじゃないでしょうか?
:augroup
... 自動コマンドのグループ:autocmd
... 自動コマンド:abbreviate
... 短縮入力:noreabbrev
や、先頭にc
,i
を付けても良い
:command
... コマンド:function
... 関数:highlight
... ハイライト:runtime syntax/hitest.vim
だと実際にハイライトされる
:let
... 変数:map
... マップ:noremap
や、先頭にc
,i
,l
,n
,o
,s
,t
,v
,x
を付けても良い
:menu
... メニュー:noremenu
や、先頭にa
,c
,i
,n
,o
,s
,tl
,v
,x
を付けても良い
開いているもの
gf
で開く用途には向きませんが、どちらも大量に開いている時はバッファに表示されると便利そうです。
:buffers
... バッファ- または
:files
,:ls
- または
:tabs
... タブ
各種操作結果
こちらも大量に表示されるような状況では便利そうです。
:registers
... レジスタ- または
:display
- または
:marks
... マーク:messages
... メッセージ:history
... コマンド履歴
Vimの情報
Vim関連の様々な情報はバッファに表示して見たいというケースはあまりないかもしれません。
個人的には代替の手段の方が使い勝手が良いと思っています。
:compiler
... コンパイラ用設定:digraphs
... ダイグラフ:h digraph-table
の方が見やすいかもしれない
:set
... 既定値と異なるオプション:setglobal
や:setlocal
もある:options
でも良いかもしれない
:version
... バージョン番号やその他の情報
デバッグ向け
Vim script開発者向け。
:breaklist
... ブレークポイント
QuickFix
切り替える際に表示するだけならわざわざバッファに表示する必要はないかもしれません。
:chistory
... grepやmakeなど、quickfixリストの履歴- locationリストの場合は
:lhistory
- locationリストの場合は
:clist
... quickfixリストの内容- locationリストの場合は
:llist
- locationリストの場合は
移動系
こちらも一時的に表示するだけならバッファに表示する必要はなさそうです。
※実際に移動できるマッピングなどがあれば...*2
:jumps
... ジャンプリスト:tags
... タグスタック:tselect
... タグ
その他
ということで最後はこちら。
:smile
... 「ついに見つけたんだね、セイバー!」
oooo$$$$$$$$$$$$oooo oo$$$$$$$$$$$$$$$$$$$$$$$$o oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o o$ $$ o$ o $ oo o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o $$ $$ $$o$ oo $ $ "$ o$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$o $$$o$$o$ "$$$$$$o$ o$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$o $$$$$$$$ $$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$$$$$$ """$$$ "$$$""""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ "$$$ $$$ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ "$$$o o$$" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$o $$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o o$$$oooo$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o$$$$$$$$$$$$$$$$$ $$$$$$$$"$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$"""""""" """" $$$$ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" o$$$ "$$$o """$$$$$$$$$$$$$$$$$$"$$" $$$ $$$o "$$""$$$$$$"""" o$$$ $$$$o o$$$" "$$$$o o$$$$$$o"$$$$o o$$$$ "$$$$$oo ""$$$$o$$$$$o o$$$$"" ""$$$$$oooo "$$$o$$$$$$$$$""" ""$$$$$$$oo $$$$$$$$$$ """"$$$$$$$$$$$ $$$$$$$$$$$$ $$$$$$$$$$" "$$$""""
golang.tokyo 静的解析Dayでgolang.org/x/tools/go/analysisを使ってみた
golang.org/x/tools/go/analysis 面白そうだなー。 けど触る時間がなかなか取れないなー。 と思っていたらちょうどタイムリーなツイートがあり、
12/8とかにGoで静的解析を一緒にモクモクやるかいをどこかでやりたいけど、会場がないので、一緒にモクモクしてくれる人とどこか会場を探してます。
— tenntennʕ ◔ϖ◔ʔ ==Go@Goが生きてる (@tenntenn) November 27, 2018
数時間後にこちらの開催が決まりました。
ということでモクモクとgscでhonnef.co/go/tools/lintからgolang.org/x/tools/go/analysis
への置き換えをやってきました。
ちなみにgsc
はgolang.tokyo #14のLT*1で紹介した自作のlinterです。
変更点をざっくりまとめると、
honnef.co/go/tools/lint
ではCheckerというinterfaceに実装をするが、golang.org/x/tools/go/analysis
ではAnalyzerというstructになるhonnef.co/go/tools/lint
ではJobを受け取ってast.Inspectで処理をしていくが、golang.org/x/tools/go/analysis
ではPassを受け取ってInspector.Preorderで処理をしていくAnalyzer
のRequires
フィールドに必要な前処理を指定する- 次の処理に結果を渡すような作りにすることも可能
- コマンド本体はlintutil. ProcessFlagSetからmultichecker.Mainになる
- ただしmainでflagを扱えなくなってしまう!
- ツール全体でExitCodeを0にするといった制御が不可に...
- なので
Pass
のReport
フィールド(関数)を書き換えることで 無理矢理 実現した
- ただしmainでflagを扱えなくなってしまう!
- テストはhonnef.co/go/tools/lint/testutilがgolang.org/x/tools/go/analysis/analysistestになる
- 使い方はだいたい同じ
といったところです。
統一的なインターフェースになって再利用しやすくなるのもメリットですが、処理速度も向上していて、 手元で3秒くらいかかっていたのが一瞬で終わるようになりました。
今日はまだgolang.org/x/tools/go/analysis
を使いこなすところまではいけませんでしたが、とても有意義な時間を過ごせました。
便利なAnalyzer
が増えるとみんな幸せになれるのでもっともっと使う人が増えていくと良いですね!
awesome analyzerを作るという気持ちを示した #golangtokyo
— tenntennʕ ◔ϖ◔ʔ ==Go@Goが生きてる (@tenntenn) December 8, 2018
VimConf 2018に行ってきた
VimConf 2018に行ってきました。
今年は@mattn_jpさんだけじゃなく、Vim作者のBramさんも発表するという超豪華Keynoteでした。
また、今年は去年以上に国際色高まるカンファレンスだったんじゃないかなと思います。
運営のみなさま、とても大変だったと思いますがすごく楽しい1日を過ごすことができました。
本当にありがとうございます。
以下セッションの一言感想です。
What is the next feature? by Yasuhiro Matsumoto
vim-jpについてのエモい話。
自分もVimにパッチを送る時、vim-jpにはとてもお世話になりました。
それにしても(ある程度形になった)パッチを用意してプレゼンに臨むのは流石です。
Vim: From hjkl to a platform for plugins by Bram Moolenaar
Vimの新機能について作者本人から話が聴けるというすごく貴重な発表でした。
プラグイン管理やVimの表現力がさらに充実していきそうで今後がとても楽しみです。
もちろんVim scriptのパフォーマンス向上も!
Migrating plugins to standard features by daisuzu
自分の発表。
Shougo wareを標準機能で置き換える話をしてきました。
Modes by Tatsuhiro Ujihisa
:Termdebug
を使ったモードの実装の解説。
前にMeguro.vimで:Termdebug
を使っているところを見せてもらいましたが、とても便利そうでした。
A day in the life of (ordinary) Vimmer by OKURA Masafumi
Ordinary Vimmerは毎朝アップデートしますよね。
Learn once, use anywhere.
自分の発表で言いたかったことはこれです。
Modern editor-independent development environment for PHP by USAMI Kenta
普段はEmacsを使っていらっしゃる。
自分でHackできるツールが良いというのは完全に同意です。
Effective Modern Vim scripting by Alisue
Create your own plugin!
すごくわかります。
vital-Whiskyの中にあるRx
がすごく気になりました。
Oni - The GUI-fication of Neovim by Akin
Electron(React + TypeScript)製のneovimクライアントの紹介。
バッファを分割してブラウザを開けるのはとても便利そうでした。
Vim ported to WebAssembly by rhysd
今日1番濃い発表だったんじゃないか...?
Vimの内部処理の解説がとても詳しくてすごく勉強になりました。
それにしても8日でやってしまうのが本当にすごい。
grpc-gatewayでProtocol Buffers over HTTP
grpc-gatewayを使うとgRPCサーバをRESTfulなインターフェースで叩けるようになります。
APIクライアントはswaggerから生成しても良いのですが、Goだとprotocで生成したstructをPOSTする方が依存も少なく、楽なこともあるでしょう。
ということで軽く試してみました。
Protocol Buffersのシリアライズ/デシリアライズ
まず、サーバがProtocol Buffersをやりとりできるようにしてあげないといけません。
そのための機能は全てgrpc-gatewayのruntimeパッケージにあるので、以下のようにしてServeMuxを初期化すればOKです。
mux := runtime.NewServeMux( runtime.WithMarshalerOption("application/octet-stream", new(runtime.ProtoMarshaller)), )
APIクライアント
これでクライアントからapplication/octet-stream
でProtocol Buffersを投げることができるようになりました。
func do(url string, in *pb.ExampleRequest) (*pb.ExampleResponse, error) { body := new(bytes.Buffer) if err := new(runtime.ProtoMarshaller).NewEncoder(body).Encode(in); err != nil { return nil, err } res, err := http.Post(url, "application/octet-stream", body) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 400 { b, err := ioutil.ReadAll(res.Body) if err != nil { return nil, errors.New(res.Status) } return nil, fmt.Errorf("%s: %s", res.Status, b) } out := new(pb.ExampleResponse) if err := new(runtime.ProtoMarshaller).NewDecoder(res.Body).Decode(out); err != nil { return nil, err } return out, err }
エラーの型を共通化する
このままでも普通に使う分には問題ありませんが、サーバがエラーレスポンスを返した際もBodyをstructにデコードできるとより嬉しいです。
現状だと、サーバ側のエラーレスポンスはデフォルトで以下のunexportedな型になっています。
type errorBody struct { Error string `protobuf:"bytes,1,name=error" json:"error"` // This is to make the error more compatible with users that expect errors to be Status objects: // https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto // It should be the exact same message as the Error field. Message string `protobuf:"bytes,1,name=message" json:"message"` Code int32 `protobuf:"varint,2,name=code" json:"code"` Details []*any.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"` } // Make this also conform to proto.Message for builtin JSONPb Marshaler func (e *errorBody) Reset() { *e = errorBody{} } func (e *errorBody) String() string { return proto.CompactTextString(e) } func (*errorBody) ProtoMessage() {}
そのため、クライアント側でデコードするためにはこちらをコピーして使うか、サーバ側で型を変更する必要があります。
エラーの型を変える場合はWithProtoErrorHandlerを使います。
mux := runtime.NewServeMux( runtime.WithMarshalerOption("application/octet-stream", new(runtime.ProtoMarshaller)), runtime.WithProtoErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, r *http.Request, err error) { w.Header().Set("Content-Type", marshaler.ContentType()) s, ok := status.FromError(err) if !ok { s = status.New(codes.Unknown, err.Error()) } buf, merr := marshaler.Marshal(s.Proto()) if merr != nil { w.WriteHeader(http.StatusInternalServerError) io.WriteString(w, `{"error": "failed to marshal error message"}`) return } w.WriteHeader(runtime.HTTPStatusFromCode(s.Code())) w.Write(buf) }), )
内容はDefaultHTTPErrorとほとんど同じですが、エラーの型をgoogle.golang.org/genproto/googleapis/rpc/status(*spb.Status
)にしています。
こうすることで、クライアント側はgoogle.golang.org/genproto/googleapis/rpc/status
をspb
*1としてimportし、proto.Unmarshal
で*spb.Status
に戻せるようになります。
if res.StatusCode >= 400 { b, err := ioutil.ReadAll(res.Body) if err != nil { return nil, errors.New(res.Status) } v := new(spb.Status) if err := proto.Unmarshal(b, v); err != nil { return nil, fmt.Errorf("%s: %s", res.Status, b) } return nil, status.ErrorProto(v) }
返ってきたエラーはstatus.FromErrorで*status.Statusにすると、Code
やMessage
、Details
が取り出せるので後続の処理で自由に使えます。
if s, ok := status.FromError(err); ok { log.Printf("code: %d, message: %s, details: %v", s.Code(), s.Message(), s.Proto().Details) }
なお、Details
は型情報が落ちてしまっているため、内容を正しく出力するにはptypes.UnmarshalAnyで元に戻してあげる必要があります。
例えばDetails
がerrdetails.BadRequestの場合だと以下のようなコードです。
if s, ok := status.FromError(err); ok { var details []string for _, d := range s.Proto().Details { var m errdetails.BadRequest if err := ptypes.UnmarshalAny(d, &m); err == nil { details = append(details, m.String()) } } log.Printf("code: %d, message: %s, details: %v", s.Code(), s.Message(), details) }
s.Details()
だと[field_violations:<field:"data" description:"invalud" > ]
、
s.Proto().Details
だと[type_url:"type.googleapis.com/google.rpc.BadRequest" value:"\n\017\n\004data\022\007invalud" ]
になってしまうのが、
ちゃんと[field_violations:<field:"data" description:"invalud" > ]
になります。
Goの標準パッケージだけでRESTfulなHandlerを作る
Goのnet/http
パッケージだけではパスパラメータを扱うことができないため、/users/:id
のようなエンドポイントを作ろうとしたら自分で処理を書かなければいけない。
適当なフレームワークや3rd-partyのパッケージを使えば簡単ではあるんだけど、時々標準パッケージだけで書きたくなってその度にどうやって書くんだっけ?となるのでblogに書いておく。
ポイントはHow to not use an http-router in goで紹介されているShiftPath
。
func ShiftPath(p string) (head, tail string) { p = path.Clean("/" + p) i := strings.Index(p[1:], "/") + 1 if i <= 0 { return p[1:], "/" } return p[1:i], p[i:] }
これを使うことでパスの最上位とそれ以降がそれぞれhead
、tail
として取得できるので、以下を繰り返しながらハンドリングしていく。
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string // それ以降のパスをr.URL.Pathに設定し、次のHandlerに渡す head, r.URL.Path = ShiftPath(r.URL.Path) // headを使って次のHandlerを決める switch head { case "users": h.users.ServeHTTP(w, r) default: http.NotFound(w, r) } }
例えば次のエンドポイントを作るとしたらこんな感じ?
func ShiftPath(p string) (head, tail string) { p = path.Clean("/" + p) i := strings.Index(p[1:], "/") + 1 if i <= 0 { return p[1:], "/" } return p[1:i], p[i:] } func NewHandler() http.Handler { return &rootHandler{ api: &apiHandler{ todos: &todosHandler{}, users: &usersHandler{}, }, } } type rootHandler struct { api *apiHandler } func (h *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = ShiftPath(r.URL.Path) switch head { case "api": h.api.ServeHTTP(w, r) default: http.NotFound(w, r) } } type apiHandler struct { todos *todosHandler users *usersHandler } func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = ShiftPath(r.URL.Path) switch head { case "todos": h.todos.ServeHTTP(w, r) case "users": h.users.ServeHTTP(w, r) default: http.NotFound(w, r) } } type todosHandler struct{} func (h *todosHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = ShiftPath(r.URL.Path) switch r.Method { case http.MethodPost: // Create Todo case http.MethodGet: // Get Todo default: http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) } } type usersHandler struct{} func (h *usersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = ShiftPath(r.URL.Path) switch r.Method { case http.MethodPost: // Create User case http.MethodGet: // Get User default: http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) } }
うん、まあ面倒。
ちなみにtodosHandler
やusersHandler
のServeHTTP
以降の処理は、
h.handler(head).ServeHTTP(w, r)
の形式で呼べるようにしても良いし、h.create(w, r)
やh.get(w, r, head)
のようにしてしまっても良い。
※ただしhead
をcontext
に詰めて渡すのはダメ!
1の場合はh.handler
がhttp.HandlerFunc
など、ServeHTTP
を実装した型を返すことになる。
もちろんstruct
を返しても構わないけど、How to correctly use context.Context in Go 1.7 – Jack Lindamood – Mediumにあるような割と複雑なコードになってしまうんじゃないかな。
力尽きたのでとりあえずこの辺で...