こんにちは。scarvizです。
今回は「Go言語でWebサーバを立てる」の記事を書いているときや、AZサーチ(http://gosrv.scarviz.net/azsearch)を作成しているときに色々試していたことを紹介します。
■http.RequestのFormValue関数について
①受け取った文字列に「;」が入っていると、それ以降を除去する
※URLエンコードをして送った場合はそのままの状態で取り出されます。
→(4/9追記)訂正。下記に追記しています。
②URLエンコードされた文字列は自動的にデコードされる
例えば、以下のようにパラメータとしてtestに「test;mes」を、test2に「test;mes」をURLエンコードした「test%3bmes」を設定して渡したとします。
package main
import (
"fmt"
"net/http"
"net/url"
)
func main() {
url, _ := url.Parse("http://localhost/testpage?test=test;mes&test2=test%3bmes")
var r http.Request
r.URL = url
var w http.ResponseWriter
Test(w, &r)
}
func Test(w http.ResponseWriter, r *http.Request) {
fmt.Println("url:", r.URL)
// 設定された文字列を取得
test := r.FormValue("test")
test2 := r.FormValue("test2")
fmt.Println("test:", test)
fmt.Println("test2:", test2)
}
結果:
url: http://localhost/testpage?test=test;mes&test2=test%3bmes test: test test2: test;mes
testの「test;mes」がFormValue関数を通ると「test」までしか取得されていません。
また、URLエンコードをしているtest2の文字列がFormValue関数を通ると自動的にデコードされています。
→(4/9追記)
「;」はセパレータなので、除去されたのではなく、上記ならmesがパラメータ扱いになっただけでした。
■レスポンスヘッダー情報にContent-typeのtext/htmlを設定するとIEでうまく表示されない
http.ResponseWriterのHeader().Add("Content-type", "text/html charset=utf-8")として、htmlファイルを表示するようにした場合、IE(IE9で確認)ではhtmlファイルをダウンロード(ファイルのダウンロードメッセージが表示される)する動きになっていました。
例えば以下のようにしている場合、test.htmlファイルをダウンロードしようとします。
func Test(w http.ResponseWriter, r *http.Request) {
// IEに対応させるには、このようにレスポンスヘッダーを設定してはいけない
w.Header().Add("Content-type", "text/html charset=utf-8")
t, _ := template.ParseFiles("test.html")
t.Execute(w, t)
}
レスポンスヘッダー情報を設定せずに返した場合、問題なくtest.htmlが表示されるようになりました。
→(4/9追記)
「"text/html charset=utf-8"」の箇所に誤りがあって、「"text/html; charset=utf-8"」とすればちゃんとIEでも表示されました。
■JSONの扱い
①JSONの値で"null"が渡される場合について
http.GetなどでレスポンスとしてJSONを受け取る場合、値に"null"が格納されていることがあります。
string型の値を扱うキーでnullだったり、int型の値を扱うキーでnullだった場合、どういう動きをするか検証してみました。
package main
import (
"encoding/json"
"fmt"
"strings"
"net/http"
"io/ioutil"
)
type Mes struct {
TestStr string
TestInt int
}
func main() {
// nullが値にある
jsonStr := `{"teststr":null,"testint":null}`
fmt.Println(jsonStr)
// http.GetなどでJSONを受け取ったと仮定して、レスポンスのBodyに格納
response := http.Response{Body:ioutil.NopCloser(strings.NewReader(jsonStr))}
// レスポンスのBodyを読み込む
data, _ := ioutil.ReadAll(response.Body)
var mes Mes
// JSONから構造体へ
json.Unmarshal(data, &mes)
fmt.Println(mes)
}
結果:
{"teststr":null,"testint":null}
{ 0}
ゼロ値に変換されています。問題なさそうですね。
→(4/9追記)訂正。下記に追記しています。
しかし、実際にhttp.GetでJSONを結果として受け取り、Unmarshalをすると以下のエラーが出力されました。
json: cannot unmarshal null into Go value of type string json: cannot unmarshal string into Go value of type int
検証方法が悪いのか、Getで返って来る時に何か別の要因があるのかまでは分かっていません。
とりあえず僕は、string型なら""(空文字)に、int型なら0というようにゼロ値に置換して対応しました。
→(4/9追記)
検証方法に誤りがありました。エラーを拾っていないので、当然エラーは出力されないというだけでした。
以下のようにエラーを取得し、出力するようにした場合、今回遭遇したnullのエラーが表示されました。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
type Mes struct {
TestStr string
TestInt int
}
func main() {
// nullが値にある
jsonStr := `{"teststr":null,"testint":null}`
fmt.Println(jsonStr)
// http.GetなどでJSONを受け取ったと仮定して、レスポンスのBodyに格納
response := http.Response{Body: ioutil.NopCloser(strings.NewReader(jsonStr))}
// レスポンスのBodyを読み込む
data, _ := ioutil.ReadAll(response.Body)
var mes Mes
// JSONから構造体へ
err := json.Unmarshal(data, &mes)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(mes)
}
結果:
{"teststr":null,"testint":null}
json: cannot unmarshal null into Go value of type string
あとはinterfaceを使えば、どんな値でも問題ないので、こちらの方が一般的のようです。
interfaceを使う例は以下になります。
package main
import (
"encoding/json"
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
// nullが値にある
jsonStr := `{"teststr":null,"testint":null}`
fmt.Println(jsonStr)
// http.GetなどでJSONを受け取ったと仮定して、レスポンスのBodyに格納
response := http.Response{Body:ioutil.NopCloser(strings.NewReader(jsonStr))}
// レスポンスのBodyを読み込む
data, _ := ioutil.ReadAll(response.Body)
// JSONをinterfaceのMapに格納する
var mes map[string]interface{}
json.Unmarshal(data, &mes)
fmt.Println(mes)
}
結果:
{"teststr":null,"testint":null}
map[testint:<nil> teststr:<nil>]
→(4/9追記)
ポインタを使うことでnullを取ることも出来るそうです。
ご指摘頂いた方が作成されたものを共有致します。
http://play.golang.org/p/G_4TOY1RT-
②JSONでリクエストする場合
JSONに変換するため用意する構造体は、エクスポートしている必要があるので、大文字から始まる名前で定義すると思います。
しかし、受け取り側が大文字を考慮していない場合、定義されていないキーとして、エラーコードが返って来る場合があります。
この場合は、Marshal関数でJSON形式に変換した後、リクエストで受け付けるJSONキー名とあわせるように、大文字を小文字に置換する必要があるようです。
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
type Mes struct {
TestStr string
TestInt int
}
func main() {
// 構造体
str := Mes{TestStr: "Test", TestInt: 10}
fmt.Println(str)
// 置換用
r := strings.NewReplacer(`"TestStr":`, `"teststr":`, `"TestInt":`, `"testint":`)
// 構造体からJSONにする
jsonStr, _ := json.Marshal(str)
// キーに大文字が存在する
os.Stdout.Write(jsonStr)
fmt.Println("") // 改行
// キーだけすべて小文字にする
jsonStr = []byte(r.Replace(string(jsonStr)))
os.Stdout.Write(jsonStr)
}
結果:
{TestStr 10}
{"TestStr":"TestStr","TestInt":10}
{"teststr":"TestStr","testint":10}
ちょっと面倒な感じがします。すべてのサービスで大文字がダメというわけではないですし。
※ちなみにGCMサーバは大文字を受け付けてくれません。
ここはぜひGoに置き換えて欲しいところですね!
→(4/8追記)
構造体を以下のようにすることで、別名として扱えるそうです。
type Mes struct {
TestStr string `json:"teststr"`
TestInt int `json:"testint"`
}
やしさん(http://kwmt27.net/)に教えて頂きました!ありがとうございます!
■GoのWebサーバを使う場合
GoのWebサーバを使う場合、一つ実施しないといけないことがあります。
まずは下記を実行してください。
service httpd status
もし、実行中になっていたら、下記を実行してください。
service httpd stop
httpdはあなたにはきっと不要なサービスですよ(たぶん)。
※少なくとも、80番ポートをGoWebサーバで使用したい場合は、80番ポートを使用しているhttpdを停止しないといけません。
みなさんのGoWebサーバに、これらがお役に立てば幸いです。
「JSONの扱い」の②に追記したものに加え、下記項目にご指摘頂いたので、追記致しました。
返信削除・「http.RequestのFormValue関数について」の①
・「レスポンスヘッダー情報にContent-typeのtext/htmlを設定するとIEでうまく表示されない」
・「JSONの扱い」の①
すごく勉強になりました!
ありがとうございます!
色々試して悩んでいたので、一瞬で解決してちょっと感動です。
構造体のフィールドのタグ(`json:"teststr"`とか)ですが、別名というより付加情報に近いです。複数設定できますし、reflectパッケージで型情報から取得できます。JSONだけではなく、xmlやMessagePackなどでリフレクションを使って、シリアライズ/デシリアライズを行なう際などに使われるようです。
返信削除なるほど。C#の属性みたいな感じですね。
削除ありがとうございます!