🐆

[Java]署名付きURLでS3にテキストファイルアップロードする

2024/01/18に公開

JavaでAWSのS3に署名付きURLを使ってテキストファイルをアップロードするソースコード(覚え書き)です。

経緯など

S3にファイルを安全にアップロードする代表的な方法として、VPCエンドポイントを経由する方法があげられますが、AWS SDK S3のバージョンが古くて対応しているクラス(AmazonS3ClientBuilder)が使えない&環境起因でSDKのアップグレードも叶わず、やむを得ず署名付きURLを使うことになりました。

本記事では、コードの作成に当たって最も苦戦した、AWSから受け取った署名付きURLを使ってテキストファイルをアップロードするパートについて、自身の覚え書きも兼ねて、ソースコードを載せておきます。

ソースコード

以下、AWSより取得した署名付きURLを使ってS3にファイルをアップロードするメソッドです(import文は割愛しています)。
今回、CSVファイルのアップロードを想定した内容(主に、コメント)にしています。

   /**
     *  CSVファイルをS3へアップロードする
     *
     *
     * @param strPresignedUrl 署名付きURL
     * @param filePath ローカル上で作成されたファイルのパス
     * @param fileName アップロードするファイル名(上位ディレクトリや拡張子も含めて指定する 例: "フォルダ名/hoge.csv")
     * @return アップロードされたファイルのS3URI
     */
    public static String fileUploadToS3(String strPresignedUrl, String filePath, String fileName) {

        String command = "curl -X PUT --upload-file " + filePath + " " + strPresignedUrl + " -o /dev/null -w '%{http_code}\\n' -s";

        try {
            String bucketName = "xxxxxxxxx";
            String key = fileName;

            Process process = Runtime.getRuntime().exec(command);
            InputStream input = process.getInputStream();

            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            StringBuffer out = new StringBuffer();

            String line;
            while ((line = reader.readLine()) != null) {
                // デフォルトだとシングルクォーテーションが入るっぽいので、削除する
                out.append(line.replace("\'", ""));
            }
            String statusStr = out.toString();
            reader.close();
            input.close();
            process.waitFor();
           
            int status = Integer.parseInt(statusStr);
            // レスポンスの結果が不正であれば例外を発生する
            if(status != 200){
                throw new Exception("S3へのファイルアップロードに失敗しました" + " ステータス:" + status + " ファイル名:" + fileName);
            }

            process.destroy();

            // アップロード先のS3URIを返却
            return "s3://" + bucketName + "/" + key;
        } catch (Exception e) {
            // 実運用で使用する場合は書き換え必須
            e.printStackTrace() ;
        }
    }

※S3のバケット名(bucketName)は、コンソールを開いた際に直下に表示されているものになります。

ポイント

(1) Runtimeクラスのexecメソッドを使ってcURLコマンドを実行

本当は、HttpClientやHttpURLConnection辺りを使いたかったのですが、

AWS SDK を使用して Amazon S3 の署名付き URL を作成する | aws
Amazon S3署名済み URL での作業 | aws
Java HttpURLConnectionを使ってファイルをアップロードする | Qiita
luankevinferreira/UploadFile.java | GitHub Gist

あたりを参考に試行錯誤してもうまくいかなかったので、やむなくRuntimeクラスのexecメソッドでcURLを実行させることにしました。

(2) HTTPステータスコードでS3アップロードの正否を判断

Processクラスには、実行結果を判定するメソッドとして、

  • waitFor()
  • exitValue()

などがありますが、これらは実行した外部プロセスに関するもので、S3アップロードの正否は判断できません。
例えば、HTTPステータスコードが403で返ってきたとしても、waitFor()やexitValue()は、そのプロセス自体に問題がなければ正常終了を表す0になってしまいます。

そこで、無理やりな感じはしますが、cURL実行の戻り値をHTTPステータスコードのみにし、その値を取得して処理の正否を判断させることにしました。
以下、HTTPステータスコードだけを取得するcURLコマンドのサンプルです。

curl "http://hogehoge.example.com" -o /dev/null -w '%{http_code}\n' -s

(3) replaceメソッドで、HTTPステータスコード回りのシングルクォーテーションを削除

環境起因の可能性もありますが、HTTPステータスコードを取得したところ、「'200'」のようにシングルクォーテーションで囲われた値が変数に入っていたので、replaceにて置換させる処理を入れました。

終わりに

公式サイト等のサンプルコードを見てもなかなかうまくいかなかったので、cURLコマンドを使うという最終手段でなんとか。

もし、HttpClientやHttpURLConnectionで実現可能だよ、という方がいらっしゃったらぜひ教えてください~!

参考

Discussion