【Go言語】net/httpパッケージでAPIリクエストを送信する

目次

はじめに

net/httpパッケージを利用してAPIリクエストを送信する方法についてまとめます。
今回はOpenWeatherAPIを利用して天気情報を取得してみました。

全体フロー

登場人物

登場する要素は下記です。

  • http.Request
    • Method
    • URL
      • クエリ
    • (もしあれば)リクエストボディで送信するデータ
    • Header
  • http.Client
  • ioutil.ReadAll
  • json.Unmarshal

フロー

大まかな全体フローは下記になります。

  1. http.NewRequestでリクエスト作成
  2. 1で作成したリクエストにクエリとヘッダーを設定
  3. http.Clientでリクエストを送信
  4. 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)
}
よかったらシェアしてね!

コメント

コメントする

CAPTCHA


目次
閉じる