こんにちは。ここ最近、Go言語をちまちま触り始めた、CDataの杉本です。
シンプルかつわかりやすい言語仕様で面白いなーと思いながら、色々と試しています。
最近公開されたMS Learnのコンテンツもわかりやすくて良いですね。
docs.microsoft.com
今回はせっかくなので、APIに対してリクエストし、構造体にデータを格納するプログラムを試してみました。
APIは以前私のBlogで紹介した、O'Reilly のブックリストを返すAPIです。
kageura.hatenadiary.jp
APIはCData API Serverを使って構成しています。
www.cdata.com
Goのバージョンは1.16を使用しました。
対象のAPIの仕様
「https://oreillydemoapi.azurewebsites.net/api.rsc/OReillyBooks/」に対してGETリクエストを投げることで、書籍の一覧が取得できます。
認証はカスタムヘッダー「x-cdata-authtoken」を使用します。
GET /api.rsc/OReillyBooks/ HTTP/1.1
Host: oreillydemoapi.azurewebsites.net
x-cdata-authtoken: 7y3E6q4b6V1v9f0D2m9j
レスポンスのサンプルはこんな感じです。
Rootとなるオブジェクトに「value」というレコードの配列を持つプロパティが存在します。このデータが書籍の一覧になっています。
{
"@odata.context": "https://oreillydemoapi.azurewebsites.net/api.rsc/$metadata#OReillyBooks",
"value": [
{
"RowId": 2,
"ImageUrl": "https://www.oreilly.co.jp/books/images/picture_large4-87311-063-7.jpeg",
"ISBN": "4-87311-063-7",
"Price": "3080",
"PublishDate": "37196",
"Title": "C++プログラミング入門 新版",
"URL": "https://www.oreilly.co.jp/books/4873110637/"
},
{
"RowId": 3,
"ImageUrl": "https://www.oreilly.co.jp/books/images/picture_large4-87311-065-3.jpeg",
"ISBN": "4-87311-065-3",
"Price": "3300",
"PublishDate": "37226",
"Title": "サーバ負荷分散技術",
"URL": "https://www.oreilly.co.jp/books/4873110653/"
}
]
}
API リクエストで使用するパッケージ
標準で組み込まれている「net/http」パッケージを使用しました。
golang.org
とりあえず単純にデータ取得だけを行いレスポンスのBodyに含まれるJSONを構造体にして、参照します。
JSON→構造体の変換は「encoding/json」パッケージです。このあたりも標準で備えているのがありがたいですね。
golang.org
プロジェクトの準備
とりあえず以下のコマンドで適当にプロジェクトを準備しました。
mkdir OReillyAPIRequestSample
cd OReillyAPIRequestSample
touch main.go
go mod init
レスポンスを格納するための構造体の準備
まずはResponseを格納する構造体を定義します。
RootとBooksの2つの構造体で成り立っていて、valueでBooks構造体の配列を保持しています。
type Root struct {
Value []Books `json:"value"`
}
type Books struct {
RowId int
ImageUrl string
ISBN string
Price string
PublishDate string
Title string
URL string
}
ここで一点注意したいのは、構造体のプロパティ名です。
Goは構造体のプロパティ名やパッケージに含まれるファンクション名の命名規則として、頭が大文字かどうかでプライベートとパブリックを区分けしています。
今回レスポンスのデータを json.Unmarshal で構造体に変換しますが、この際に元のJSONの値に従って「value」として定義して、そのままプライベート扱いにしてしまうと変換が行われません。
golang.org
そのため、元のプロパティ名と関連付けるために「json:"value"
」を設定しています。
API リクエストを行う
「net/http」を使って一番簡単にGETリクエストを行う方法は
「http.Get」メソッドを利用する方法です。
ただ、この「http.Get」メソッドはカスタムヘッダーなどを追加で登録することができません。
そのため、「http.NewRequest」メソッドで一度リクエスト内容を定義して、リクエストを管理する「new(http.Client)」を生成し、「client.Do(req)」で生成したリクエストを送信します。
url := "https://oreillydemoapi.azurewebsites.net/api.rsc/OReillyBooks/"
authHeaderName := "x-cdata-authtoken"
authHeaderValue := "7y3E6q4b6V1v9f0D2m9j"
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.Header.Set(authHeaderName, authHeaderValue)
client := new(http.Client)
resp, err := client.Do(req)
NewRequestのエラーの返り値はメソッドの名前やURL文字列のバリデーションを行っています。今回は無視しました。
client.Do(req)の戻り値エラーはリクエスト前のバリデーションチェックやサーバーと疎通できなかった場合のTimeoutが発生した場合のエラーになるようです。
サーバーからのレスポンスとして、Service Unavailable や 401 Unauthroized 等が発生した際には、ResponseのStatusCodeを確認する必要があります。なんとなく、client.Do(req)のエラーに含まれそうかもと思ったんですが、API用パッケージというよりはHTTPリクエストのためのパッケージなので、納得です。
if err != nil {
fmt.Println("Error Request:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Println("Error Response:", resp.Status)
return
}
あとはResponseBodyのJSONを読み取りし、「json.Unmarshal」で構造体に反映させます。これで無事データを取得できました。
body, _ := io.ReadAll(resp.Body)
var Books Root
json.Unmarshal(body, &Books)
fmt.Printf("%-v", Books)
ソースコード全体
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
fmt.Println("Start!")
url := "https://oreillydemoapi.azurewebsites.net/api.rsc/OReillyBooks/?$top=3"
authHeaderName := "x-cdata-authtoken"
authHeaderValue := "7y3E6q4b6V1v9f0D2m9j"
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.Header.Set(authHeaderName, authHeaderValue)
client := new(http.Client)
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error Request:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Println("Error Response:", resp.Status)
return
}
fmt.Printf("%-v", resp)
body, _ := io.ReadAll(resp.Body)
var Books Root
json.Unmarshal(body, &Books)
fmt.Printf("%-v", Books)
}
type Root struct {
Value []Books `json:"value"`
}
type Books struct {
RowId int
ImageUrl string
ISBN string
Price string
PublishDate string
Title string
URL string
}
関連コンテンツ