Waka blog.

[Go]キャッシュを活用しつつVimeo APIリクエストを行う

Vimeoのサムネイル画像をgoで実装しているwebサーバーで取得する必要が出てきた。

go-vimeoというサードパーティのライブラリが存在するが、今回はサムネイル画像取得だけが目的なので、直接APIリクエスト処理を実装した。

以下実装

func NewRepository(logger Logger, redisAddrs []string) *repository {
	rdb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs: redisAddrs,
	})
	return &repository{
		logger: logger,
		cache:  rdb,
	}
}

// ② APIの戻り値の型
type VideoThumbnailData struct {
	Active         bool   `json:"active"`
	BaseLink       string `json:"base_link"`
	DefaultPicture bool   `json:"default_picture"`
	Link           string `json:"link"`
	ResourceKey    string `json:"resource_key"`
	Sizes          []struct {
		Height             int    `json:"height"`
		Link               string `json:"link"`
		LinkWithPlayButton string `json:"link_with_play_button"`
		Width              int    `json:"width"`
	} `json:"sizes"`
	Type string `json:"type"`
	URI  string `json:"uri"`
}

func (r *repository) GetThumbnailURL(ctx context.Context, VimeoAccessToken, vimeoVideoID string) (thumbnailUrl string, err error) {
        // ⑤ キャッシュ
	cachedUrl, err := r.cache.Get(ctx, vimeoVideoID).Result()
	if err == nil {
		return cachedUrl, nil
	} else if err != redis.Nil {
		r.logger.Errorf("%v", err)
		return "", err
	}

        // ① Vimeoのサムネイル画像取得APIを活用
	url := fmt.Sprintf("https://api.vimeo.com/videos/%s/pictures", vimeoVideoID)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		r.logger.Errorf("%v", err)
		return "", err
	}

        // ③ アクセストークン認証
	req.Header.Set("Authorization", "Bearer "+VimeoAccessToken)

	client := &http.Client{}

	resp, err := client.Do(req)
	if err != nil {
		r.logger.Errorf("%v", err)
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK { 
                // ④ ログ出力
		var errorResponse struct {
			Error string `json:"error"`
		}

		if decErr := json.NewDecoder(resp.Body).Decode(&errorResponse); decErr == nil {
			r.logger.Errorf("Failed with status %v: %v", resp.StatusCode, errorResponse.Error)
		} else {
			r.logger.Errorf("Failed with status %v, and could not decode error response: %v", resp.StatusCode, decErr)
		}

		return "", err
	}

	var data struct {
		Data []VideoThumbnailData `json:"data"`
	}

	if decErr := json.NewDecoder(resp.Body).Decode(&data); decErr != nil {
		r.logger.Errorf("%v", decErr)
		return "", decErr
	}

	for _, thumbnail := range data.Data {
		if thumbnail.Active {
                        // ⑤ キャッシュ
			err := r.cache.Set(ctx, vimeoVideoID, thumbnail.BaseLink, 1*time.Hour).Err()
			if err != nil {
				r.logger.Errorf("%v", err)
			}

			return thumbnail.BaseLink, nil
		}
	}

	return "", nil
}

解説

  • ① Vimeoのサムネイル画像取得APIを活用
    • サムネイル取得専用のAPIが存在していたためそれを利用する。
    • 単純にvideoID指定のgetリクエストで動画のメタ情報を全部取得しその中からサムネイル画像を抽出することもできるが、処理が煩雑になる
  • ② APIの戻り値の型
    • 戻り値の型もドキュメントがあったので、そこからコードに起こした。(= VideoThumbnailData )
  • ③ アクセストークン認証
  • ④ ログ出力
    • Vimeo APIでエラーが発生した場合、errオブジェクトはnilなのでレスポンスの内容を解析してログに出力している
  • ⑤ キャッシュ
    • Vimeo APIは1分あたりのアクセス数制限があり、上限を超えると429エラー(Too Many Requests)となる
    • これを回避するため、VideoIDをキーにAPIのレスポンスをキャッシュに保存している
    • 今回はgo-redisを使用

以上