ChatGPTにコードを書かせてみた ~ AWS S3 ファイルアップロード ~

red green and blue text 開発

はじめに

Github CopilotやChatGPTなどソフトウェアエンジニアがコードを書くアシストをしてくれそうなツールの進化が目覚ましい昨今。プログラマー不要説はともかくとして効率よく仕事をするために必須のツールになるのは間違いないと感じます。

そこでChatGPTにやりたいことを自然言語で伝えて出力されたコードをみていきます。

ChatGPTのようなツールを使いこなして仕事の生産性を高めていきましょう。

お題

AWS S3にPre-signed URLを使用してファイルをアップロードする処理をGolangで書きたい。

以下にChatGPTが生成したコードをそのまま貼ってます。

ちゃんと前提条件もメッセージとして出力されました。

以下のコードを実行する前に、AWS SDK for Goがインストールされていることを確認してください。

生成されたコード

package main

import (
    "fmt"
    "net/http"
    "os"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3/s3manager"
)

func main() {
    // S3バケット名
    bucket := "example-bucket"
    // S3オブジェクトキー(ファイル名)
    key := "example.txt"
    // Pre-signed URLを発行するためのクライアント
    svc := s3manager.NewUploader(session.Must(session.NewSession(&aws.Config{
        Region: aws.String("ap-northeast-1"),
    })))
    // Pre-signed URLを発行
    req, _ := svc.UploadRequest(&s3manager.UploadInput{
        Bucket: aws.String(bucket),
        Key:    aws.String(key),
    })
    // Pre-signed URLを取得
    urlStr, headers, _ := req.PresignRequest(15 * 60) // 15分間有効なURLを発行

    // アップロードするファイル
    file, err := os.Open("example.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // HTTPクライアント
    client := &http.Client{}

    // HTTPリクエストを作成
    req, err = http.NewRequest("PUT", urlStr, file)
    if err != nil {
        panic(err)
    }

    // HTTPリクエストヘッダーにPre-signed URLに含まれるヘッダーを設定
    for header, value := range headers {
        req.Header.Set(header, value)
    }

    // HTTPリクエストを実行
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }

    // HTTPレスポンスを出力
    fmt.Println(resp.Status)
}

生成されたコードを見ていくと、まずはPre-signed URLをAWS SDKを使って取得するコードが書かれています。そして取得したURLに対して"example.txt"というファイルをPUTリクエストで送信するという流れです。

aws-sdk-goがv2ではないのがちょっと気になりますかね。

コードの改善

いくつかChatGPTに追加のリクエストをして上記で生成されたコードの改善をしてみます。

  • Golangのaws-sdkはv2を使いたい
  • S3のバケット名とオブジェクトキーをコマンドライン引数から取得
  • Pre-signed URLを取得する処理とファイルをアップロードする処理をそれぞれ別の関数にする
  • コマンドライン引数で、Pre-signed URLを取得だけする、ファイルアップロードだけする、全ての処理をするのを選択できるようにする

このようなことをChatGPTにお願いして生成されたコードが以下になります。

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	// コマンドライン引数をパースする
	bucketName := flag.String("bucket", "", "Bucket name")
	objectKey := flag.String("key", "", "Object key")
	op := flag.String("op", "", "Operation (url, upload)")
	flag.Parse()

	switch *op {
	case "url":
		// Pre-signed URLを生成する
		signedURL, err := generatePresignedURL(*bucketName, *objectKey)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		fmt.Println(signedURL)

	case "upload":
		// ファイルをアップロードする
		err := uploadFile(*bucketName, *objectKey)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

	default:
		// Pre-signed URLを生成して、ファイルをアップロードする
		signedURL, err := generatePresignedURL(*bucketName, *objectKey)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		err = uploadFileWithHTTP(*objectKey, signedURL)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}

// Pre-signed URLを生成する関数
func generatePresignedURL(bucketName string, objectKey string) (string, error) {
	// AWS SDKを初期化する
	cfg, err := config.LoadDefaultConfig(context.Background())
	if err != nil {
		return "", err
	}
	s3Client := s3.NewFromConfig(cfg)

	// Pre-signed URLを生成するためのリクエストを作成する
	req := s3Client.GetObjectRequest(&s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
	})

	// Pre-signed URLを取得する
	signedURL, err := req.Presign(15 * time.Minute)
	if err != nil {
		return "", err
	}

	return signedURL, nil
}

// HTTP PUTリクエストを送信してファイルをアップロードする関数
func uploadFileWithHTTP(objectKey string, signedURL string) error {
	// アップロードするファイルを読み込む
	file, err := os.Open(objectKey)
	if err != nil {
		return err
	}
	defer file.Close()

	// リクエストヘッダーに設定するContent-Lengthを取得する
	stat, err := file.Stat()
	if err != nil {
		return err
	}
	size:= stat.Size()

	// HTTP PUTリクエストを作成する
	req, err := http.NewRequest(http.MethodPut, signedURL, file)
	if err != nil {
		return err
	}

	// Content-Lengthを設定する
	req.ContentLength = size

	// HTTP PUTリクエストを送信する
	client := http.DefaultClient
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	// レスポンスを読み込む
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}

	fmt.Println(string(body))
	return nil
}

上記のコードでは、コマンドライン引数から-opフラグを使用して、Pre-signed URLを生成するだけ、ファイルをアップロードするだけ、またはPre-signed URLを生成してファイルをアップロードするかどうかを指定できます。

また、Pre-signed URLを生成する際には、generatePresignedURL関数が呼び出され、ファイルをアップロードする際には、uploadFileWithHTTP関数が呼び出されます。

リクエストヘッダーには、Content-Lengthを設定する必要があります。このため、ファイルのサイズを取得してContent-Lengthに設定しています。

だいぶ思ったようなコードになった気がします。

これが動けば完成ということでこのままコピペして実際に動くのか試してみます。

動作確認

結論、そのままではコンパイル通りませんでした。

importや関数名が間違っているところなどコンパイルが通らない部分だけ手直しをして実行してみたところ動作しました。

動作確認ができたソースコードは以下のリポジトリに置きました。

まとめ

ChatGPTを使ってざっくり処理の流れを書かせることはできますが、それをそのまま使えるほどの精度は今のところなさそうです。

とはいえだいぶ良いところまで来ているので今後の進化には期待できそうです。

もう少し色々と試してみたいなと思いました。

コメント

タイトルとURLをコピーしました