Open8

Golangでサーバレスのバッチ処理を構築する

nabetsunabetsu

HTTP Request

  • http.Gethttp.Post
    • シンプルなリクエストを送信する場合
  • http.Client
    • HTTP client headers, redirect policyやその他の設定を行う場合

http.Get

package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	url := "https://google.co.jp"

	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("Error")
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

http.Client

クライアント(http.Client)とリクエスト(http.Request)とリクエストを実際に発行する(client.Do)という構造が見えると見通しが良くなると思います。http.Get ではこれらをデフォルトのクライアント(DefaultClient) を用いてよしなにリクエストを発行しているのです。

package main

import (
	"fmt"
	"io"
	"net/http"
)

const AIRTABLE_API_KEY = "keyrJp9239YmiN7tk"

func main() {
	url := "https://google.co.jp"

	client := &http.Client{}
	resp, err := client.Get(url)
	if err != nil {
		fmt.Println("Error")
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	fmt.Println(string(body))
}

Query Parametersの設定

req.URL.Queryで設定する

        url := "https://api.airtable.com/v0/app9NApDWCvgUEs1J/Beer"

	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", AIRTABLE_API_KEY))

	params := req.URL.Query()
	params.Add("maxRecords", "1")
	req.URL.RawQuery = params.Encode()

参考資料

nabetsunabetsu

Twitterの認証

一連の流れ

基本的にはOAuth 2.0 | Docs | Twitter Developer Platform に記載された流れの通りやればOK。

Bearer Tokenの取得

func twitter_oauth() string {
	const API_KEY = ""
	const API_SECRET_KEY = ""
	const OAUTH2_ENDPOINT = "https://api.twitter.com/oauth2/token"

	client := &http.Client{}
	req, _ := http.NewRequest("POST", OAUTH2_ENDPOINT, nil)

	// Basic認証情報を設定
	req.SetBasicAuth(API_KEY, API_SECRET_KEY)

	// Query Parameterを設定
	params := req.URL.Query()
	params.Add("grant_type", "client_credentials")
	req.URL.RawQuery = params.Encode()

	// リクエストの実行
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	fmt.Println(string(body))

	token_data := new(token)
	if err := json.Unmarshal(body, token_data); err != nil {
		fmt.Println("JSON Unmarshal error:", err)
	}

	return token_data.Access_token
}

取得したTokenを使用してTweetを検索

func search_tweets(bearer string) {
	const SEARCH_ENDPOINT = "https://api.twitter.com/2/tweets/search/recent"
	client := &http.Client{}
	req, _ := http.NewRequest("GET", SEARCH_ENDPOINT, nil)

	// 認証情報を設定
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", bearer))

	// Query Parameterを設定
	params := req.URL.Query()
	params.Add("query", "ヨロッコビール")
	params.Add("tweet.fields", "geo,context_annotations,created_at,in_reply_to_user_id,lang,referenced_tweets,attachments")
	req.URL.RawQuery = params.Encode()

	// リクエストの実行
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	fmt.Println(string(body))

}

認証周りで使ったGoの書き方

認証情報の設定

Bearer Token取得時にBasic Authenticationを行う必要があったが、req.SetBasicAuthを使えばOKだった。

// Basic認証情報を設定
req.SetBasicAuth(API_KEY, API_SECRET_KEY)

Go Basic認証 - Qiita

ページネーション

公式ページの以下の記載の通り、基本的にはAPIのレスポンスにあるメタ情報のnext_tokenに値が存在するかどうかで次のページの存在有無を判定すればいい。

The recent search endpoints will respond to a query with at least one page, and provide a next_token in its JSON response if additional pages are available. To receive matching Tweets, this process can be repeated until no token is included in the response.
Search Tweets - How to paginate | Docs | Twitter Developer Platform

nabetsunabetsu

AWSのリソースにアクセス

V2

GoのSDKにはv2が出ているので、今からやるならv2を使ったほうが良いかも(Go1.15以上が必要)

基本的な流れ

AWSのリソースへのアクセスの流れ

service clientをまず作成する必要がある。
service clientはlow-level accessを提供する。

Using the AWS SDK for Go V2 with AWS Services | AWS SDK for Go V2

NewFromConfigにConfigを渡すことでclientを定義する。

import "context"
import "github.com/aws/aws-sdk-go-v2/config"
import "github.com/aws/aws-sdk-go-v2/service/s3"

// ...

cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
	panic(err)
}

client := s3.NewFromConfig(cfg)

S3にアクセス

$ go get -u github.com/aws/aws-sdk-go
$ go get -u github.com/aws/aws-sdk-go-v2/service/s3

指定したバケットのオブジェクトを一覧表示するコードが以下。

cfg, err := config.LoadDefaultConfig(
	context.TODO(), 
	config.WithRegion("ap-northeast-1"))
if err != nil {
	log.Fatalf("unable to load SDK config, %v", err)
}

client := s3.NewFromConfig(cfg)
output, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
	Bucket: aws.String("YOUR_BUCKET_NAME"),
})
if err != nil {
	log.Fatal(err)
}

log.Println("first page results:")
for _, object := range output.Contents {
	log.Printf("key=%s size=%d", aws.ToString(object.Key), object.Size)
}

バケット名の指定が間違っていると以下のエラーメッセージが出る。何か追加の指定が必要なのかと思ったが、正しいバケット名を指定すれば正常にアクセスできたので注意。

operation error S3: ListObjectsV2, https response error StatusCode: 301, RequestID: 35BCD7E3V53YAGE5, HostID: vixRL81uKRGJQrAA5kN6YLqOCScuxSZtPVLuvlhyAMYzvEwM5v8I7HBNrqMFDR0Sx+fbgzQMtNo=, api error PermanentRedirect: The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.
nabetsunabetsu

ファイルのアップロード

aws-doc-sdk-examples/PutObjectv2.go at main · awsdocs/aws-doc-sdk-examples

上記のサンプルを元に構築すれば一旦S3へのアップロードは出来た。
Clientを使う方法だが、参考資料にあるようにハイレベルのライブラリを使ったほうが楽に構築できる?

Bodyに指定できるのはio.Readerになる。

StructなどGoの型をそのままアップロードする場合

Convert a struct to io.Reader in Go (Golang) を参考にした。json.NewEncoderbytes.Bufferを渡せばいいみたい。

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("ap-northeast-1"))
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	client := s3.NewFromConfig(cfg)

	// S3へのアップロード
	bucketName := "golang-sample-s3-bucket"
	
	var buf bytes.Buffer
	json.NewEncoder(&buf).Encode(jsonData)

	input := &s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(filename),
		Body:   &buf,
	}
	_, err = PutFile(context.TODO(), client, input)

参考資料