- 投稿日:2019-07-30T18:09:46+09:00
Golangで郵便番号から住所を取得するAPIを作成する
1. Google Maps API の登録と設定をする
https://cloud.google.com/maps-platform/?hl=ja
※ 今回使用するのは Geocoding API
2. Google Maps API を直接叩いてみる
以下のような URL でアクセスして、JSON形式で値が返ってきていれば OK.
https://maps.googleapis.com/maps/api/geocode/json?address=105-0003&language=ja&sensor=false&key=APIキー
参考
3. Go で上述のAPIを呼ぶ
基本的には以下のサンプルをもとに進めれば良い。
https://github.com/googlemaps/google-maps-services-go/blob/master/examples/geocoding/cmdline/main.go
ただ、はじめはなるべくミニマムで動かしたいので最低限の記述に変更する。
main.gopackage main import ( "context" "flag" "fmt" "log" "googlemaps.github.io/maps" ) type Address struct { Prefecture string `json:"prefecture"` Locality string `json:"locality"` } var ( apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.") language = flag.String("language", "ja", "The language in which to return results.") region = flag.String("region", "JP", "The region code, specified as a ccTLD two-character value.") ) func main() { flag.Parse() var client *maps.Client var err error client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) outputFatalLog(err) req := &maps.GeocodingRequest{ Address: "105-0003", Language: *language, Region: *region, } resp, err := client.Geocode(context.Background(), req) outputFatalLog(err) address := Address{ Prefecture: resp[0].AddressComponents[2].LongName, Locality: resp[0].AddressComponents[1].LongName, } fmt.Println(address) } func outputFatalLog(err error) { if err != nil { log.Fatalf("fatal error: %s", err) } }
go run main.go
を実行して{東京都 港区}
と出力されれば OK.4. API っぽくする
3
の状態では、固定された住所(東京都港区)しか返却できないので、受け取った郵便番号を元に住所を取得して、その結果を返却するAPIを作成する。main.gopackage main import ( "context" "encoding/json" "flag" "log" "net/http" "github.com/julienschmidt/httprouter" "googlemaps.github.io/maps" ) type Address struct { Prefecture string `json:"prefecture"` Locality string `json:"locality"` } type Error struct { Message string `json:"message"` } var ( apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.") language = flag.String("language", "ja", "The language in which to return results.") region = flag.String("region", "JP", "The region code, specified as a ccTLD two-character value.") ) func main() { router := httprouter.New() router.GET("/v1/:postalCode", SearchAddressByPostalCode) log.Fatal(http.ListenAndServe(":8080", router)) } func SearchAddressByPostalCode(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { flag.Parse() w.Header().Set("Content-Type", "application/json; charset=UTF-8") var client *maps.Client var err error if *apiKey != "" { client, err = maps.NewClient(maps.WithAPIKey(*apiKey)) } else { createErrorResponse("APIキーを指定してください", w, http.StatusBadRequest) return } outputFatalLog(err) req := &maps.GeocodingRequest{ Address: ps.ByName("postalCode"), Language: *language, Region: *region, } resp, err := client.Geocode(context.Background(), req) outputFatalLog(err) if len(resp) == 0 { createErrorResponse("住所が見つかりませんでした", w, http.StatusNotFound) return } address := Address{ Prefecture: resp[0].AddressComponents[2].LongName, Locality: resp[0].AddressComponents[1].LongName, } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(address) } func createErrorResponse(msg string, w http.ResponseWriter, statusCode int) { errorResponse := Error{ Message: msg, } w.WriteHeader(statusCode) json.NewEncoder(w).Encode(errorResponse) } func outputFatalLog(err error) { if err != nil { log.Fatalf("fatal error: %s", err) } }
go run main.go
で起動し、別タブでcurl -v --silent localhost:8080/v1/105-0003 | jq
のようにアクセスして、以下のようなレスポンスが返ってきていれば OK.> GET /v1/105-0003 HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json; charset=UTF-8 < Date: Tue, 30 Jul 2019 07:16:31 GMT < Content-Length: 47 < { [47 bytes data] * Connection #0 to host localhost left intact { "prefecture": "東京都", "locality": "港区" }参考
http://sgykfjsm.github.io/blog/2016/03/13/golang-json-api-tutorial/
おまけ
apiKey = flag.String("key", "APIキー", "API Key for using Google Maps API.")
手元でテストする分には良いが、APIキーをこのままコミットしてしまうと大変なことになるので、環境変数から取得できるように改修する。
.envGOOGLE_MAP_API_KEY=hogefugaやり方は色々あるかもしれないが、自分は以下のように書き換えた。
main.goapiKey := os.Getenv("GOOGLE_MAP_API_KEY") if apiKey == "" { panic("APIキーを指定してください") return } client, err = maps.NewClient(maps.WithAPIKey(apiKey))あとがき
今回は Google Maps API をラップしたAPIを自作したことになる。
直接クライアントから Google Maps API を呼んでも問題ないが、わざわざラップするメリットはいくつかある。
- APIキーの管理が楽
- 呼び出し元のクライアントが複数になると、APIキーをクライアントそれぞれで管理することになる
- レスポンスを改造できる
{"prefecture_id": "13", "prefecture": "東京都", "locality": "港区" }
みたいなことができる- Google Maps API のインタフェースが変わったときも安心
- あまりないと思うが、そのままクライアント側で呼び出すと Google Maps API のレスポンスの形式が変更されたときにバグる可能性がある
- 投稿日:2019-07-30T17:14:34+09:00
型アサーション
プログラミング言語Goを読んでいて、型アサーションの説明が分かりづらかったのでメモ
型アサーション
型アサーションはインタフェースの値に使えるインタフェース型の変換のようなものである
io.Writerインタフェース型の値があるとする
var w io.Writer w = os.Stdout fmt.Printf("type = %T\n", w) // type = *os.Fileこの時、wが別のインタフェース型になれるか調べることができる
rw := w.(io.ReadWriter) // 成功する(*os.File)型はReadおよびWriteのメソッドを持っているため成功する。
rwでは、ReadWriterで定義されているメソッドReadも追加で呼ぶことができる失敗した場合はpanicが発生するが、成功したかという変数を受け取ればpanicは発生しない
var w io.Writer w = new(ByteCounter) // ByteCounterは、ReadメソッドがないためReadWriterインタフェースは満たせない rw := w.(io.ReadWriter) // panicが発生する rw, ok = w.(io.ReadWriter) // rwにはゼロ値、okにはfalseが入る
- 投稿日:2019-07-30T13:59:34+09:00
GO言語で簡単なチャット機能の作成
GO言語によるWEBアプリケーション開発
という、上記の書籍を見ながら2章まで進めると簡易的なチャットアプリが作成出来ました。
(とても良い本です!)まず、1章まで進めると以下のようにさらに簡易的なチャットアプリを作成出来ます。
1章では正しいビルドの実行方法や、
並行処理の便利さや
ユニットテストの方法などなど1章だけでも私には十分すぎる内容でした!
1章でもエラーが出て苦戦したり、小一時間悶々とすることも多々ありました・・。
エラー内容としては単純な凡ミスばかりで気付いた時の爽快感を味わいつつ、
2章を進めて行くと、
先ほどのメッセージに加えて送信者の名前も表示されるようになり
他のユーザーと会話することも可能になります。ユーザー名に関しては以下のように認証機能を追加して、
選択できるようにしてあります。今回はGoogleアカウントを使用して、
先ほどの画像のようにテスト太郎アカウントと私(青文字)で認証して
メッセージの送受信が行えるようになりました。複数のタブを開き(今回は2つでしたが)
localhost:8080/login ページでサインインするユーザを選べば
何人とでも複数会話が実行が可能になります。3章ではプロフィール画像追加について学んでいけるので、
1章・2章で躓いた部分、不明点などを復習してから
次に進んでいきたいと思います!
- 投稿日:2019-07-30T12:45:20+09:00
ぼく 「[]runeを範囲外でスライスしたら当然エラー出るだろうなあ」
ぼく 「あれ?」
Go言語_[]runeのスライスstr := "0123456" runes := []rune(str) // 範囲内 fmt.Println(string(runes[0])) // 0 fmt.Println(string(runes[3])) // 3 fmt.Println(string(runes[4:6])) // 45 fmt.Println(string(runes[4:7])) // 456 // 範囲外、、のはずだけど fmt.Println(string(runes[4:8])) // 456 <= エラー出ないのか、、 fmt.Println(string(runes[10])) // エラー: index out of range <= わかる fmt.Println(string(runes[10:20])) // (空文字が出力)ぼく 「範囲外のインデックス指定してるんだから、普通 "xxx out of range" 的なエラーが出るでしょ」
ぼく 「Go言語、まさかスライスで範囲外指定してもいいように察してくれるのか、?」
ぼく 「ここ範囲外だからとりあえず空文字返しとこ、的な」
ぼく 「いやいやいや、、」
ぼく 「stringでやってみよ」Go言語_string配列のスライスstrArray := []string{"0", "1", "2", "3", "4", "5", "6"} // 範囲内 fmt.Println(strArray[0]) // 0 fmt.Println(strArray[3]) // 3 fmt.Println(strings.Join(strArray[4:6], "")) // 45 fmt.Println(strings.Join(strArray[4:7], "")) // 456 // 範囲外 fmt.Println(strings.Join(strArray[4:8], "")) // エラー: slice bounds out of range fmt.Println(strArray[10]) // エラー: index out of range fmt.Println(strings.Join(strArray[10:20], "")) // エラー: slice bounds out of rangeぼく 「普通にエラー出るな、、」
ぼく 「これならわかる、けどさっきの[]runeの範囲外スライスはなんでエラー出ないんだ」
ぼく 「スライスのスライスだからか、、? []runeは確か配列じゃなくてスライスだったな、(※配列の参照、Goには可変長配列がないので代わりにこれをつかう)」
ぼく 「、、stringスライスのスライスならどうだろ」Go言語_stringスライスのスライスvar strSlice []string strSlice = append(strSlice, "0", "1", "2", "3", "4", "5", "6") fmt.Println(strings.Join(strSlice[4:8], "")) // エラー: slice bounds out of range fmt.Println(strSlice[10]) // エラー: index out of range fmt.Println(strings.Join(strSlice[10:20], "")) // エラー: slice bounds out of rangeぼく 「変わらないな、、」
ぼく 「スライスのスライスは範囲外でもエラー出ない、とかいう話ではないのか」
ぼく 「なんでruneのスライスだけ、範囲外でスライスしてもエラー出ないんだろう」
ぼく 「(わからんtaskete)」追記(2019/07/31) : 親切な方(aimofさん)降臨
ぼく 「神は実在した…(激しく感謝)」
ぼく 「aimofさんありがとうございます」
ぼく 「というかcapってなんだ」
ぼく 「キャップだから…帽子か?」調べてみた
- cap ( capacity ) : 配列の容量
▲ 要素数(length)とは異なるぼく 「Go言語にはlengthだけじゃなくて、capacityとかあるのか…」
ぼく 「で、[]rune(str)でキャストするときには、なんか暗黙的にcapacity大きく取られてたってことか」
ぼく 「みてみよ」str := "0123456" runes := []rune(str) fmt.Println(cap([]byte(str))) // 32 fmt.Println(cap(runes)) // 32 // capacity内 fmt.Println(string(runes[4:32])) // 456 // capacity外 fmt.Println(string(runes[4:33])) // slice bounds out of rangeぼく 「ほんとだ、、runeスライスへのキャストで、だいぶ大きめにcapacity取られてるな」
ぼく 「32もある…」
ぼく 「なんでこの数なんだろ」(下の方で少し試した(おまけ)ことあります)
とりあえずまとめ
[]runeは範囲外でスライスしてもいいように察してくれる(エラー出ない)それ以外の配列・スライスなら範囲外スライスは普通にエラーがでる- 配列・スライスのlen, capの問題だった
- makeでcapを指定、とかではなく[]rune(str)でキャストすると、capが勝手にある程度の値を取ってくれる
- len (=length) : 配列の要素数 ( ◁ わかる )
- cap (=capacity) : 配列の容量 ( ◁ 知らなかった )
- capの範囲内であれば、例え配列の要素が存在せずとも slice bounds out of range は出ない
補足
- string: 文字列を示すbyteスライスのイミュータブル参照、Go言語ではstr[0]とか書くと1文字目ではなくひとつ目のbyte値が得られる
- rune: Unicodeコードポイント、ざっくりいうと文字1つひとつに割り当てられたコード値. runeのスライスはstringに変換できる
追記(2019/07/31) : おまけ
// ▼ 1文字の場合 s := "1" rs := []rune(s) // length fmt.Println(len(rs)) // 1 // capacity ( <= 1文字でもとりあえず32確保する模様) fmt.Println(cap([]byte(s))) // 32 fmt.Println(cap(rs)) // 32 // ▼ 32文字の場合 st := "12345678901234567890123456789012" rus := []rune(st) // length fmt.Println(len(rus)) // 32 // capacity ( <= 32文字なら、まだcapacityも32のまま) fmt.Println(cap([]byte(st))) // 32 fmt.Println(cap(rus)) // 32 // ▼ 33文字の場合 str := "123456789012345678901234567890123" runes := []rune(str) // length fmt.Println(len(runes)) // 33 // capacity ( <= 32を超えて初めて増加! ) fmt.Println(cap([]byte(str))) // 48 fmt.Println(cap(runes)) // 36
- 投稿日:2019-07-30T10:06:44+09:00
Go初学メモ
単なるメモです。まとまってきたら良い感じにまとめて記事にします。
インタフェース
- goにはOSとの緩衝材になってくれるインタフェースという概念がある→わかる
- インタフェースがあることで、OS側のファイルディスクリプタ(メモリ番地的な、アドレス的なもの)を指定しなくても良くなっている
- インタフェースは、構造体などの具象型が持つべきメソッドを表現している
- クラスのような概念も含まれている?
- ということは
構造体 = インスタンス
?構造体
- 構造体はインタフェースで定義されているメソッドを持つ
- 具体的なメソッド内容の記述は構造体の中で行う
- ということはインタフェースはあくまで構造体へメソッドをマッピングするためにあるような立ち位置?
- 構造体とインタフェースの関係性は
N:1
?N:N
?- メソッドの定義方法は↓
main.gofunc (r Receiver) MethodName { 'メソッドにやらせたい処理' }
- 全体的に
変数名 型
みたいな定義をしているっぽい。型 変数名
で定義してきた自分(よわよわUnityマン)にはびっくり- 構造体をそのまま実行するわけではなくて、インタフェースに書かれた(なんて言うんだろう、定義とは言わない気がする)メソッドを構造体で具体的に書いてあげて、それをmainのfunctionの中でimportというか宣言してあげて、実行するみたいな手続きを踏んでるくさい
- インタフェースでOSとの橋渡しを、構造体で実際のfunctionとの橋渡しをしているみたいな役割分担?
- たしかにシステムコールとかするならこの構成が柔軟な気がする
他言語で言うクラスはインタフェースなのか?構造体なのか?