RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

Goでconnpass APIを叩いて出力するまでやってみた

f:id:mako_makok:20191224160255p:plain

こんにちは、新卒のmako_makokです。 今回はGo未経験者が外部のAPIを叩いてみた話になります。

はじめに

自分でカスタマイズしたクエリを使用して勉強会情報を取得したいと考えました。
普段はこういったツールは個人的にPythonで書くことが多いのですが、ある程度使い慣れた言語でリクエストを投げてパースするだけだとすぐに実装が終わってしまうので、
勉強がてら興味があったGoで実装してみました。
本日はGoで
1. connpass APIを叩き
2. レスポンスを構造体に詰め
3. 出力
するまでの手順や使用したツールについてお話ししようと思います。

この記事でやること

  • やること
    • レスポンスのjsonに対応した型の作成
    • httpクライアントの生成からGETリクエストを送る
    • Jsonを定義した型に変換する

型を作る

今回は以下のような型を定義しました。

package types

import (
    "time"
)

type RequestParam struct {
    EventID int
    Keyword []string
    KeywordOR []string
    YM []int
    YMD []int
    NickName []string
    OwnerNickname []string
    SeriesID int
    Start int
    Order int
    Count int
    Format string
}

type ResultSet struct {
    ResultsReturned int `json:"results_returned"`
    Events []Event      `json:"events"`
    ResultsStart     int `json:"results_start"`
    ResultsAvailable int `json:"results_available"`
}

type Event struct {
    EventURL      string `json:"event_url"`
    EventTypes     string `json:"event_type"`
    OwnerNickname string `json:"owner_nickname"`
    Series           Series `json:"series"`
    UpdatedAt        time.Time `json:"updated_at"`
    Lat              string    `json:"lat"`
    StartedAt        time.Time `json:"started_at"`
    HashTag          string    `json:"hash_tag"`
    Title            string    `json:"title"`
    EventID          int       `json:"event_id"`
    Lon              string    `json:"lon"`
    Waiting          int       `json:"waiting"`
    Limit            int       `json:"limit"`
    OwnerID          int       `json:"owner_id"`
    OwnerDisplayName string    `json:"owner_display_name"`
    Description      string    `json:"description"`
    Address          string    `json:"address"`
    Catch            string    `json:"catch"`
    Accepted         int       `json:"accepted"`
    EndedAt          time.Time `json:"ended_at"`
    Place            string    `json:"place"`
}

type Series struct {
    URL   string `json:"url"`
    ID    int    `json:"id"`
    Title string `json:"title"`
}

レスポンスの型を書くのは単純作業ですし、タイプ量も少なくないので地味に面倒な作業ですが、Goの場合以下のツールで爆速で書くことができました。

JSON-to-Go: Convert JSON to Go instantly

jsonを対応したstructとタグを作成してくれます。 今回の場合ですと、

https://connpass.com/api/v1/event/?keyword=python | jq'.'

などして、整形したjsonをコピペするだけでtypeを生成してくれます。

f:id:mako_makok:20191224142652p:plain
JsonToGo

リクエス

ちょっとしたGETをするのであればhttp.Getで良いですが、今回は勉強のためClientの設定なども行います。
標準ライブラリのnet/httpを使用します。

package httpwrapper

import (
    "io"
    "io/ioutil"
    "net/http"
    "net/url"
    "time"
)

func DoRequest(method, path string, values url.Values, body io.Reader) ([]byte, error) {
    client := &http.Client {
        Timeout: 20 * time.Second,
    }

    req, err := http.NewRequest(method, path, body)
    if err != nil {
        return nil, err
    }
    req.URL.RawQuery = values.Encode();

    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    data, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    return data, nil
}

標準ライブラリしか使っていませんが、すごくすっきり書けて気持ちがいいです。 順番に説明していきます。すっきりしすぎてあまり説明することが無いような気がしますが、それもGolangの魅力かなと思いました。

    client := &http.Client {
        Timeout: 20 * time.Second,
    }

タイムアウト値を20秒に設定します。

req, err := http.NewRequest(method, path, body)

コンストラクタを使用してRequestオブジェクトを生成しています。

  • パラメータの設定
req.URL.RawQuery = values.Encode();

valuesはmap型です。パラメータと値がマッピングされており、 Values.Encode()でURLエンコードを行います。

    resp, err := client.Do(req)
(略)
    defer resp.Body.Close()

deferを使用すると遅延実行することができます。
今回の場合ですと、DoRequest()が実行され終了する間際にresp.Body.Close()が走り、クローズ処理が行われます。

  • 読み書きできる形にする
data, err := ioutil.ReadAll(resp.Body)

ioutil.ReadAllでレスポンスボディをバイトのスライスにして返します。

connpass APIを叩いてみる

package main

import (
    "fmt"
    "encoding/json"
    "log"
    "strconv"
    "net/url"
    "./httpwrapper"
    "./types"
)

func main() {
    const endpoint = "https://connpass.com/api/v1/event/"
    keywordor := []string{"JavaScript", "TypeScript", "Go", "Java", "Kotlin",
                          "Vue", "React", "Nuxt", "Next", "Spring Boot"}
    keyword := []string{"東京"}
    count := 30
    param := types.RequestParam{Keyword: keyword, KeywordOR: keywordor, Count: count}
    values := url.Values{"keyword": keyword, "keywordor": param.KeywordOR, "count": {strconv.Itoa(count)}}

    data, err := httpwrapper.DoRequest("GET", endpoint, values, nil)
    if err != nil {
        log.Fatal(err)
    }
 
    var rs types.ResultSet
    if err := json.Unmarshal(data, &rs); err != nil {
        log.Fatal(err)
    }

    for i, event := range rs.Events {
        fmt.Println(strconv.Itoa(i + 1) + " Title: " + event.Series.Title)
    }
}

リクエストを実行します。 結果を取得できた後はencoding/jsonjson.Unmarshalメソッドでパースできます。

var rs types.ResultSet
if err := json.Unmarshal(data, &rs); err != nil {
    log.Fatal(err)
}

だいたいのjsonがこれでパースできます。*1
なお、構造体をJsonにパースする場合はjson.Marshalを使用します。

実行結果

f:id:mako_makok:20191225090147p:plain
出力結果

最後に

今回はGoでconnpass APIを叩いてみました。
今後はリクエストの送信だけではなく、 RESTなAPIの作成したり、テスト等を書いて拡充していきたいと思います。

*1:jsonvalueに複数の型が混じった配列があるとエラーになります

Copyright © RAKUS Co., Ltd. All rights reserved.