読者です 読者をやめる 読者になる 読者になる

タオルケット体操

サツバツいんたーねっと

Go言語でAjaxのPOSTリクエストを受け取る際は注意したほうがいいおはなし

プログラミング Go Web

Go言語のnet/httpのRequestはFormValue以外の方法でPOSTされた値を自動でパースするようなメソッドがないとかそういうの

JavaScriptのxhrライブラリについてのはなし

最近、僕はだいたいReactを使ってフロントを書いており、どうしても必要なときをのぞいてjQueryを利用することは少なくなってきました。
ということでJavaScriptのajaxリクエストのためだけにjQueryのような巨大なライブラリをロードするのも馬鹿らしいので、isomorphicを意識した専用のライブラリを利用しています。

代表的なものに visionmedia/superagent · GitHubmzabriskie/axios · GitHub があります。
最初はsuperagentを使ってましたけど、最近はPromiseベースのAPIの分かりやすさからaxiosを使ってます。若干ドキュメントが貧弱な感も否めませんが、まぁなんとかなるレベルです。

閑話休題。

Go言語のWebフレームワークとnet/httpのはなし

Go言語のWebフレームワークですが、思想の問題か、素のhttpライブラリの出来が良いからか知りませんがリクエストオブジェクトなんかは基本的に net/http を流用する形で実装されています。
んで、表題のように http.Request ですが、FormValue形式による値のPOSTにしか対応していないようです。詳しいところはhttps://golang.org/src/net/http/request.go#L864 のソースコードとか https://golang.org/pkg/net/http/#Request のドキュメントを参照してください。

ここで問題が発生します。
jQueryの post はFormValueの形式でサーバーに値を送るので問題ないんですが、superagentやaxiosの場合、特に何も指定しない限りはRequestPayload本体にJSON形式で値を乗っけてPOSTします。

つまり、Go言語のサーバ側でいくら r.FormValue("hoge") しようとも期待する値はとれないということです。僕はイマイチここらへんの知見がうすいんですけど、階層の深いJSONをPOSTしたいときとかは、FormエレメントのシノニムにすぎないFormValueよりもRequestPayloadのほうが有利なわけで、現代的なライブラリはそちらが前提になっているんでしょう。
あ、あと確かAngularJSのajaxリクエストもRequestPayload形式ですね。

RequestPayloadに乗っけているJSONを取得するコードの例

ひとまずgojiに渡すhandler形式を例にしますけど、net/httpに乗っかっているWAFであればどれも変わらないでしょう。

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
    "fmt"
)

type HogeRequestBody struct {
    Foo string
    Bar string
}


func Hoge(c web.C, w http.ResponseWriter, r *http.Request) {
    // NOTE: 実用するなら、Content-Typeがapplication/jsonかどうかの判断なんかも必要かも
    body, _ := ioutil.ReadAll(r.Body)

    var posted HogeRequestBody
    json.Unmarshal(body, &posted)
    foo := posted.Foo
    bar := posted.Bar
    fmt.Println(foo)
    fmt.Println(bar)

    fmt.Fprintf(w, "gaccha!")
}

とまぁこんな感じです。
ようは Request.Body を読み込んでJSONに変換してるだけです。そんだけです。

対策

そういえば curl コマンドもオプションによってはFormValueじゃなかった気がします。

標準ライブラリのほうでいい感じに吸収してくれるAPI欲しいなーとおもったんですが、そもそも僕の解釈が正しいのかよとか、具体的な実装を考えると自前の構造体へのマッピングだとかの点で微妙っぽさが拭えないので妙案だしたいところですね。
いまいちRailsとかをちゃんと使ったことがあんまりないわけで、他言語の有名ライブラリがここら辺をどういう風に実装しているのか気になってきました。っていうかちょっと中途半端すぎないすかね。

といいつつもここら辺は、各々が実装するサーバーのセキュリティ対策だとかにも関わってくるわけで、例えばgojiを使っているならばそれぞれがいい感じのmiddlewareを作ったりするしかないのかなーとおもいますね。
とりあえず、POSTやPUTを受け取るたびに各ハンドラが毎回ヘッダをみてパースして……なんてやってらんないわけで、できれば(可能な形式であれば)FormValue形式でもBodyにJSONを埋め込む方式でもどちらでも受け取れるような雰囲気で実装できたら幸せ度高いとおもいます。

それっぽい案がおもいついたらこのブログに追記しようかなとおもいます。

まとめ

  • JSでrequestを送るときはライブラリの差異を考慮にいれる

  • Go言語でJSONをPOST(PUTとかもだけど)するときは受け取る形式に気をつけたほうがよい

  • GoでWebアプリを書くとちょいちょいライブラリや機能の点でかゆいところに手が届きにくくてめんどい気持ちになったりすることがある

  • いまいちGoの型システムを使いこなせていないのでもうちょっと頑張りたい

  • この記事の内容自体は半年前にハマってて、文章にせずに放置してたら最近またハマったのでこういうのはちゃんと文章にしたほうが良い

ニュービーなので何かしら勘違いがあるかもしれないですが、以上です。