はじめに
net/httpパッケージを利用してAPIリクエストを送信する方法についてまとめます。
今回はOpenWeatherAPIを利用して天気情報を取得してみました。
全体フロー
登場人物
登場する要素は下記です。
- http.Request
- Method
- URL
- クエリ
- (もしあれば)リクエストボディで送信するデータ
- Header
- http.Client
- ioutil.ReadAll
- json.Unmarshal
フロー
大まかな全体フローは下記になります。
- http.NewRequestでリクエスト作成
- 1で作成したリクエストにクエリとヘッダーを設定
- http.Clientでリクエストを送信
- Jsonデータのパース
事前準備
OpenWeatherAPIのAPIキー取得
OpenWeatherにアクセスし、右上の「Sing In」ボタンから会員登録します。
会員登録が完了したら、マイページの「API Keys」からAPIキーを発行してください。
発行できたら下記URLの{API KEY}
を発行したAPIキーに置き換えて、ブラウザで叩きます。天気情報が返却されれば成功です。
今回はOne Call APIを利用して時間毎の天気情報を取得します。緯度(lat)、経度(lon)は検索したい場所の緯度経度に適宜置き換えていただければと思います。
▼API
https://api.openweathermap.org/data/2.5/onecall?lat=35.7114441&lon=139.7638057&exclude=minutely,daily&appid={API Key}
▼返却されるデータ
{"lat":35.7114,"lon":139.7638,"timezone":"Asia/Tokyo","timezone_offset":32400,"current":{"dt":1627633233,"sunrise":1627588019,"sunset":1627638456,"temp":301.7,"feels_like":305.59,"pressure":1002,"humidity":74,"dew_point":296.61,"uvi":0.29,"clouds":75,"visibility":10000,"wind_speed":0.89,"wind_deg":238,"wind_gust":1.79,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"rain":{"1h":0.13}},"hourly":[{"dt":1627632000,"temp":301.7,"feels_like":305.59,"pressure":1002,"humidity":74,"dew_point":296.61,"uvi":0.29,"clouds":75,"visibility":10000,"wind_speed":3.99,"wind_deg":168,"wind_gust":3.47,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0.57},
・・・・・・ 以下省略
APIリクエスト実装
http.NewRequestでリクエスト作成
func NewRequest(method, url string, body io.Reader) (*Request, error)
メソッドを利用してHTTPリクエストを作成します。第一引数にリクエストメソッド、第二引数にリクエスト先URL、第三引数にリクエストボディで送信するデータを入れて呼び出すことで、HTTPリクエストを作成することができます。
func NewRequest(method, url string, body io.Reader) (*Request, error)
OpenWeatherのOneCallAPIを利用するためのHTTPリクエストを作成する場合、下記のように記述します。GETでは基本的に第三引数は使用しないためnil
とします。
method := "GET"
url := "https://api.openweathermap.org/data/2.5/onecall"
req, err := http.NewRequest(method, url, nil)
if err != nil {
log.Fatalf("NewRequest err=%s", err.Error())
}
リクエストにクエリとヘッダーを設定
続いて作成したリクエストにクエリとヘッダーを設定します。まず、func (u *URL) Query() Values
メソッドを利用してクエリを格納するためのマップを取得し、そこにAdd
メソッドでクエリ内容を設定します。そして最後に、クエリをEncodeしてreq.URL.RawQuery
に代入すれば設定完了です。クエリに”&”が含まれていると、区切り文字としてみなされてしまうため、Encode処理を忘れないよう注意してください。
q := req.URL.Query()
q.Add("lat", "35.7114441")
q.Add("lon", "139.7638057")
q.Add("exclude", "minutely,daily")
q.Add("appid", "XXXXXXXXXXXXXXXXXXXXXX") // 発行したAPIキーを記述する
req.URL.RawQuery = q.Encode()
続いてヘッダーを設定します。Request.Header.Add(key, value string)
メソッドを用いて代入することが出来ます。
OneCallAPIでは特に何も設定しなくても大丈夫です。
// 必要に応じてHeaderを設定
req.Header.Add("Content-Type", "application/json")
http.Clientでリクエストを送信
続いてリクエストの送信です。送信するには、httpライブラリに含まれるClient
構造体を利用します。
Client構造体が持つfunc (c *Client) Do(req *Request) (*Response, error)
メソッドに、上記のステップで作成したリクエストを引数にして実行することでリクエストを送信することができます。
返却されたレスポンスに含まれるBodyはdefer resp.Body.Close()
で関数終了時にクローズするにしましょう。
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Client.Do err=%s", err.Error())
}
defer resp.Body.Close()
正しくデータを取得できているか確認するために出力します。以下のような出力結果がPrintされれば取得成功です。
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("ioutil.ReadAll err=%s", err.Error())
}
fmt.Println(string(body))
▼出力結果
{"lat":35.7114,"lon":139.7638,"timezone":"Asia/Tokyo","timezone_offset":32400,"current":{"dt":1627633233,"sunrise":1627588019,"sunset":1627638456,"temp":301.7,"feels_like":305.59,"pressure":1002,"humidity":74,"dew_point":296.61,"uvi":0.29,"clouds":75,"visibility":10000,"wind_speed":0.89,"wind_deg":238,"wind_gust":1.79,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"rain":{"1h":0.13}},"hourly":[{"dt":1627632000,"temp":301.7,"feels_like":305.59,"pressure":1002,"humidity":74,"dew_point":296.61,"uvi":0.29,"clouds":75,"visibility":10000,"wind_speed":3.99,"wind_deg":168,"wind_gust":3.47,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0.57},
・・・・・・ 以下省略
Jsonデータのパース
しかし、実際にはこのままでは利用しづらいため、取得データを扱うためのstructへとパースすることが一般的な流れとなります。少し複雑ですが、取得データを扱うためのStructを用意しました。
type Response struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Timezone string `json:"timezone"`
TimezoneOffset int `json:"timezone_offset"`
Current Current `json:"current"`
Hourly []Hourly `json:"hourly"`
}
type Current struct {
Dt int `json:"dt"`
Temp float64 `json:"temp"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
Weather []Weather `json:"weather"`
Rain Rain `json:"rain,omitempty"`
}
type Hourly struct {
Dt int `json:"dt"`
Temp float64 `json:"temp"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
Weather []Weather `json:"weather"`
Pop float64 `json:"pop"`
Rain Rain `json:"rain,omitempty"`
}
type Weather struct {
ID int `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
}
type Rain struct {
OneH float64 `json:"1h"`
}
func Unmarshal(data []byte, v interface{}) error
メソッドを利用してjsonパースします。
response := Response{}
err = json.Unmarshal(body, &response)
if err != nil {
log.Fatalf("json.Unmarshal err=%s", err.Error())
}
デバッグしてresponse変数の中身を確認すると、以下のようにStructに格納できていることが確認できます。
まとめ
APIリクエストの送信方法は以上になります。
最後にソースコード全体を掲載します。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Response struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Timezone string `json:"timezone"`
TimezoneOffset int `json:"timezone_offset"`
Current Current `json:"current"`
Hourly []Hourly `json:"hourly"`
}
type Current struct {
Dt int `json:"dt"`
Temp float64 `json:"temp"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
Weather []Weather `json:"weather"`
Rain Rain `json:"rain,omitempty"`
}
type Hourly struct {
Dt int `json:"dt"`
Temp float64 `json:"temp"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
Weather []Weather `json:"weather"`
Pop float64 `json:"pop"`
Rain Rain `json:"rain,omitempty"`
}
type Weather struct {
ID int `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
}
type Rain struct {
OneH float64 `json:"1h"`
}
func main() {
method := "GET"
url := "https://api.openweathermap.org/data/2.5/onecall"
req, err := http.NewRequest(method, url, nil)
if err != nil {
log.Fatalf("NewRequest err=%s", err.Error())
}
q := req.URL.Query()
q.Add("lat", "35.7114441")
q.Add("lon", "139.7638057")
q.Add("exclude", "minutely,daily")
q.Add("appid", "XXXXXXXXXXXXXXXXXXX")
req.URL.RawQuery = q.Encode()
// 必要に応じてHeaderを設定する。
// req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Client.Do err=%s", err.Error())
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("ioutil.ReadAll err=%s", err.Error())
}
fmt.Println(string(body))
response := Response{}
err = json.Unmarshal(body, &response)
if err != nil {
log.Fatalf("json.Unmarshal err=%s", err.Error())
}
fmt.Println(response)
}
コメント